diff --git a/.formatter.exs b/.formatter.exs
index ac394c7..2d7172e 100644
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -25,7 +25,7 @@ locals_without_parens = ~w[
marker mask mesh meshgradient meshpatch meshrow metadata mpath path pattern polygon
polyline radialGradient rect set solidcolor stop svg switch symbol text
textPath tspan unknown use view
-]a |> Enum.flat_map(fn e -> [{e, :*}, {:"#{e}!", :*}] end)
+]a |> Enum.map(fn e -> {e, :*} end)
[
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a9d758e..dda566a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
otp: [23.x, 24.x]
- elixir: [1.10.x, 1.12.x]
+ elixir: [1.13.x]
steps:
- uses: actions/checkout@v2
@@ -20,14 +20,14 @@ jobs:
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
with:
path: |
deps
_build
- key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
+ key: ${{ runner.os }}-mix-${{matrix.otp}}-${{matrix.elixir}}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
- ${{ runner.os }}-mix-
+ ${{ runner.os }}-mix-${{matrix.otp}}-${{matrix.elixir}}-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
@@ -36,77 +36,72 @@ jobs:
- name: Run Tests
run: mix test
- integration_tests:
- runs-on: ubuntu-latest
- name: Integration Test (${{matrix.elixir}}/${{matrix.otp}})
- defaults:
- run:
- working-directory: "./integration_test/temple_demo"
+ # integration_tests:
+ # runs-on: ubuntu-latest
+ # name: Integration Test (${{matrix.elixir}}/${{matrix.otp}})
+ # defaults:
+ # run:
+ # working-directory: "./integration_test/temple_demo"
- strategy:
- matrix:
- otp: [23.x]
- elixir: [1.9.x, 1.11.x]
+ # services:
+ # db:
+ # image: postgres:12
+ # env:
+ # POSTGRES_USER: postgres
+ # POSTGRES_PASSWORD: postgres
+ # POSTGRES_DB: temple_demo_test
+ # ports: ['5432:5432']
+ # options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
- services:
- db:
- image: postgres:12
- env:
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: postgres
- POSTGRES_DB: temple_demo_test
- ports: ['5432:5432']
- options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
+ # steps:
+ # - uses: actions/checkout@v2
+ # - uses: erlef/setup-beam@v1
+ # with:
+ # otp-version: 24.x
+ # elixir-version: 1.13.x
- steps:
- - uses: actions/checkout@v2
- - uses: erlef/setup-beam@v1
- with:
- otp-version: ${{matrix.otp}}
- elixir-version: ${{matrix.elixir}}
+ # - uses: actions/cache@v3
+ # with:
+ # path: |
+ # deps
+ # _build
+ # key: ${{ runner.os }}-mix-24-1.13-${{ hashFiles('**/mix.lock') }}
+ # restore-keys: |
+ # ${{ runner.os }}-mix-24-1.13-
- - uses: actions/cache@v2
- with:
- path: |
- deps
- _build
- key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
- restore-keys: |
- ${{ runner.os }}-mix-
+ # - name: Install Dependencies
+ # if: steps.cache.outputs.cache-hit != 'true'
+ # run: mix deps.get
- - name: Install Dependencies
- if: steps.cache.outputs.cache-hit != 'true'
- run: mix deps.get
+ # - name: Run Tests
+ # run: mix test || mix test --failed || mix test --failed
+ # env:
+ # MIX_ENV: test
- - name: Run Tests
- run: mix test || mix test --failed || mix test --failed
- env:
- MIX_ENV: test
-
- - uses: actions/upload-artifact@v2
- if: failure()
- with:
- name: screenshots
- path: screenshots/
+ # - uses: actions/upload-artifact@v2
+ # if: failure()
+ # with:
+ # name: screenshots
+ # path: screenshots/
formatter:
runs-on: ubuntu-latest
- name: Formatter (1.11.x.x/23.x)
+ name: Formatter (1.13.x.x/24.x)
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
- otp-version: 23.x
- elixir-version: 1.11.x
- - uses: actions/cache@v2
+ otp-version: 24.x
+ elixir-version: 1.13.x
+ - uses: actions/cache@v3
with:
path: |
deps
_build
- key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
+ key: ${{ runner.os }}-mix-24-1.13-${{ hashFiles('**/mix.lock') }}
restore-keys: |
- ${{ runner.os }}-mix-
+ ${{ runner.os }}-mix-24-1.13-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
diff --git a/.tool-versions b/.tool-versions
index 84bf208..1b09c5a 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-elixir 1.11.3
-erlang 23.2.6
+elixir ref:v1.13.4
+erlang 25.0-rc2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2beda2d..be7342e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,20 @@
## Main
+### 0.9.0-rc.0
+
+### Breaking Changes
+
+- Requires Elixir 1.13+
+- Whitespace control is now controlled by whether you use `do/end` or `:do` syntax. The `:do` syntax will render "tight" markup.
+- Components are no longer module based. Any function can now be a component. Now to render a component, you pass a function reference `c &my_component/1`.
+ - Temple.Component has been removed, which removes the `render/1` macro for defining a component. Now all you need to do is define a function and have it take an `assigns` parameter and call the `temple/1` macro that is imported from `Temple`.
+ - The `defcomp` macro has been removed, since now all you need is a function.
+- All Phoenix related things and dependencies have been removed. If you are going to use Temple with Phoenix, now use the [temple_phoenix](https://github.com/mhanberg/temple_phoenix) package instead.
+- Config options have changed. Now all you can configure are the aliases (unchanged from before) and now you can configure the EEx.Engine to use. By default it uses `EEx.SmartEngine`.
+
+Please see the guides for more in depth migration information.
+
## 0.8.0
### Enhancements
diff --git a/README.md b/README.md
index 0b23d73..6431435 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,11 @@
-# ![](temple.png)
+# ![](temple-github-image.png)
[![Actions Status](https://github.com/mhanberg/temple/workflows/CI/badge.svg)](https://github.com/mhanberg/temple/actions)
[![Hex.pm](https://img.shields.io/hexpm/v/temple.svg)](https://hex.pm/packages/temple)
-> You are looking at the README for the main branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.8.0).
+> You are looking at the README for the main branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.9.0).
-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).
+Temple is an Elixir DSL for writing HTML.
## Installation
@@ -16,8 +14,7 @@ Add `temple` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
- {:temple, "~> 0.8.0"},
- {:phoenix_live_view, "~> 0.16"} # if you are using Phoenix LiveView
+ {:temple, "~> 0.9.0-rc.0"}
]
end
```
@@ -26,16 +23,16 @@ end
Currently Temple has the following things on which it won't compromise.
- Will only work with valid Elixir syntax.
-- Should always work with normal EEx, as well as Phoenix and Phoenix LiveView.
+- Should work in all web environments such as Plug, Aino, Phoenix, and Phoenix LiveView.
## Usage
-Using Temple is 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. The runtime result of the macro is your HTML.
-See the [documentation](https://hexdocs.pm/temple/Temple.html) for more details.
+See the [guides](https://hexdocs.pm/temple/your-first-template.html) for more details.
```elixir
-use Temple
+import Temple
temple do
h2 do: "todos"
@@ -66,40 +63,43 @@ end
### Components
-Temple components provide an ergonomic API for creating flexible and reusable views. Unlike normal partials, Temple components can take slots, which are similar [Vue](https://v3.vuejs.org/guide/component-slots.html#named-slots).
+Temple components are simple to write and easy to use.
-For example, if I were to define a `Card` component, I would create the following module.
+Unlike normal partials, Temple components have the concept of "slots", which are similar [Vue](https://v3.vuejs.org/guide/component-slots.html#named-slots). You can also refer to HEEx and Surface for examples of templates that have the "slot" concept.
+
+Please see the [guides](https://hexdocs.pm/temple/components.html) for more details.
```elixir
-defmodule MyAppWeb.Components.Card do
- import Temple.Component
+defmodule MyAppWeb.Component do
+ import Temple
- render do
- section do
- div do
- slot :header
- end
+ def card(assigns) do
+ temple do
+ section do
+ div do
+ slot :header
+ end
- div do
- slot :default
- end
+ div do
+ slot :default
+ end
- div do
- slot :footer
+ div do
+ slot :footer
+ end
end
end
end
end
```
-And we could use the component like so
+Using components is as simple as passing a reference to your component function to the `c` keyword.
```elixir
-# lib/my_app_web/views/page_view.ex
-alias MyAppWeb.Components.Card
+import MyAppWeb.Component
# lib/my_app_web/templates/page/index.html.exs
-c Card do
+c &card/1 do
slot :header do
@user.full_name
end
@@ -117,87 +117,17 @@ c Card do
end
```
-### Phoenix templates
+### Engine
-To use temple as a Phoenix Template engine, you'll need to configure the right file extensions with the right Temple engine.
+By default, Temple will use the `EEx.SmartEngine` that is built into the Elixir standard library. If you are a web framework that uses it's own template engine (such as [Aino](https://github.com/oestrich/aino) and Phoenix/LiveView, you can configure Temple to it!
```elixir
-# config.exs
-config :phoenix, :template_engines,
- exs: Temple.Engine
- # or for LiveView support
- # this will work for files named like `index.html.lexs`
- # you can enable Elixir syntax highlighting in your editor
- lexs: Temple.LiveViewEngine
+# config/config.exs
-# If you're going to be using live_view, make sure to set the `:mode` to `:live_view`.
-# This is necessary for Temple to emit markup that is compatible.
-config :temple, :mode, :live_view # defaults to normal
-
-# 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)$"
- ]
- ]
+config :temple,
+ engine: Aino.View.Engine # or Phoenix.HTML.Engine or Phoenix.LiveView.Engine
```
-```elixir
-# app.html.exs
-
-""
-html lang: "en" do
- head do
- meta charset: "utf-8"
- meta http_equiv: "X-UA-Compatible", content: "IE=edge"
- meta name: "viewport", content: "width=device-width, initial-scale=1.0"
- title do: "YourApp · Phoenix Framework"
-
- _link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css")
- end
-
- body do
- header do
- section class: "container" do
- nav role: "navigation" do
- ul do
- li do
- a href: "https://hexdocs.pm/phoenix/overview.html"), do: "Get Started"
- end
- end
- end
-
- a href: "http://phoenixframework.org/", class: "phx-logo" do
- img src: Routes.static_path(@conn, "/images/phoenix.png"),
- alt: "Phoenix Framework Logo"
- end
- end
- end
-
- main role: "main", class: "container" do
- p class: "alert alert-info", role: "alert", do: get_flash(@conn, :info)
- p class: "alert alert-danger", role: "alert", do: get_flash(@conn, :error)
-
- render @view_module, @view_template, assigns
- end
-
- script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
- end
-end
-```
-
-### Tasks
-
-#### temple.gen.layout
-
-Generates the app layout.
-
-#### temple.gen.html
-
-Generates the templates for a resource.
-
### Formatter
To include Temple's formatter configuration, add `:temple` to your `.formatter.exs`.
@@ -209,7 +139,13 @@ To include Temple's formatter configuration, add `:temple` to your `.formatter.e
]
```
+## Phoenix
+
+To use with [Phoenix](https://github.com/phoenixframework/phoenix), please use the [temple_phoenix](https://github.com/mhanberg/temple_phoenix) package! This bundles up some useful helpers as well as the Phoenix Template engine.
+
## Related
- [Introducing Temple: An elegant HTML library for Elixir and Phoenix](https://www.mitchellhanberg.com/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/)
- [Temple, AST, and Protocols](https://www.mitchellhanberg.com/temple-ast-and-protocols/)
+- [Thinking Elixir Episode 92: Temple with Mitchell Hanberg](https://podcast.thinkingelixir.com/92)
+- [How EEx Turns Your Template Into HTML](https://www.mitchellhanberg.com/how-eex-turns-your-template-into-html/)
diff --git a/config/config.exs b/config/config.exs
index 8233fe9..d1186fe 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,3 +1,3 @@
-use Mix.Config
+import Config
-import_config "#{Mix.env()}.exs"
+import_config "#{config_env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
index d2d855e..becde76 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -1 +1 @@
-use Mix.Config
+import Config
diff --git a/config/test.exs b/config/test.exs
index 50b5a38..a4b79a0 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,8 +1,4 @@
-use Mix.Config
-
-# this is to make the warning go away,
-# Temple does not use a json_library
-config :phoenix, json_library: Temple
+import Config
config :temple,
aliases: [
diff --git a/guides/components.md b/guides/components.md
new file mode 100644
index 0000000..921983f
--- /dev/null
+++ b/guides/components.md
@@ -0,0 +1,240 @@
+# Components
+
+Temple has the concept of components, which allow you an expressive and composable way to break up your templates into re-usable chunks.
+
+A component is any arity-1 function that take an argument called `assigns` and returns the result of the `Temple.temple/1` macro.
+
+## Definition
+
+Here is an example of a simple Temple component. You can observe that it seems very similar to a regular Temple template, and that is because it is a regular template!
+
+```elixir
+defmodule MyApp.Components do
+ import Temple
+
+ def button(assigns) do
+ temple do
+ button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
+ @text
+ end
+ end
+ end
+end
+```
+
+## Usage
+
+To use a component, you will use the special `c` keyword. This is called a "keyword" because it is not a function or macro, but only exists inside of the `Temple.temple/1` block.
+
+The first argument will be the function reference to your component function, followed by any assigns.
+
+```elixir
+defmodule MyApp.ConfirmDialog do
+ import Temple
+ import MyApp.Components
+
+ def render(assigns) do
+ temple do
+ dialog open: true do
+ p do: "Are you sure?"
+ form method: "dialog" do
+ c &button/1, class: "border border-white", text: "Yes"
+ end
+ end
+ end
+ end
+end
+```
+
+## Slots
+
+Temple components can take "slots" as well. This is the method for providing dynamic content from the call site into the component.
+
+Slots are defined and rendered using the `slot` keyword. This is similar to the `c` keyword, in that it is not defined using a function or macro.
+
+### Default Slot
+
+The default slot can be rendered from within your component by passing the `slot` the atom `:default`. Let's redefine our button component using slots.
+
+```elixir
+defmodule MyApp.Components do
+ import Temple
+
+ def button(assigns) do
+ temple do
+ button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
+ slot :default
+ end
+ end
+ end
+end
+```
+
+You can pass content through the "default" slot of your component simply by passing a `do/end` block to your component at the call site. This is a special case for the default slot.
+
+```elixir
+defmodule MyApp.ConfirmDialog do
+ import Temple
+ import MyApp.Components
+
+ def render(assigns) do
+ temple do
+ dialog open: true do
+ p do: "Are you sure?"
+ form method: "dialog" do
+ c &button/1, class: "border border-white" do
+ "Yes"
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+### Named Slots
+
+You can also define a "named" slot, which allows you to pass more than one set of dynamic content to your component.
+
+We'll use a "card" example to illustrate this. This example is adapted from the [Surface documentation](https://surface-ui.org/slots) on slots.
+
+#### Definition
+
+```elixir
+defmodule MyApp.Components do
+ import Temple
+
+ def card(assigns) do
+ temple do
+ div class: "card" do
+ header class: "card-header", style: "background-color: @f5f5f5" do
+ p class: "card-header-title" do
+ slot :header
+ end
+ end
+
+ div class: "card-content" do
+ div class: "content" do
+ slot :default
+ end
+ end
+
+ footer class: "card-footer", style: "background-color: #f5f5f5" do
+ slot :footer
+ end
+ end
+ end
+ end
+end
+```
+
+#### Usage
+
+```elixir
+def MyApp.CardExample do
+ import Temple
+ import MyApp.Components
+
+ def render(assigns) do
+ temple do
+ c &card/1 do
+ slot :header do
+ "A simple card component"
+ end
+
+ "This example demonstrates how to create components with multiple, named slots"
+
+ slot :footer do
+ a href="#", class: "card-footer-item", do: "Footer Item 1"
+ a href="#", class: "card-footer-item", do: "Footer Item 2"
+ end
+ end
+ end
+ end
+end
+```
+
+## Passing Data Through Slots
+
+Sometimes it is necessary to pass data from a component definition back to the call site.
+
+Let's look at what a `table` component could look like.
+
+#### Definition
+
+```elixir
+defmodule MyApp.Components do
+ import Temple
+
+ def cols(items) do
+ items
+ |> List.first()
+ |> Map.keys()
+ |> Enum.sort()
+ end
+
+ def table(assigns) do
+ temple do
+ table do
+ thead do
+ tr do
+ for col <- cols(@entries) do
+ tr do: String.upcase(to_string(col))
+ end
+ end
+ end
+
+ tbody do
+ for row <- @entries do
+ tr do
+ for col <- cols(@entries) do
+ td do
+ slot :cell, %{value: row[cell]}
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+#### Usage
+
+When we render the slot, we can pattern match on the data passed through the slot. If this seems familiar, it's because this is the same syntax you use when writing your tests using `ExUnit.Case.test/3`.
+
+```elixir
+def MyApp.TableExample do
+ import Temple
+ import MyApp.Componens
+
+ def render(assigns) do
+ temple do
+ section do
+ h2 do: "Inventory Levels"
+
+ c &table/1, entries: @item_inventories do
+ slot :cell, %{value: value} do
+ case value do
+ 0 ->
+ span class: "font-bold" do
+ "Out of stock!"
+ end
+
+ level when is_number(level) ->
+ span do
+ "#{level} in stock"
+ end
+
+ _ ->
+ span do: value
+ end
+ end
+ end
+ end
+ end
+ end
+end
+```
diff --git a/guides/getting-started.md b/guides/getting-started.md
new file mode 100644
index 0000000..a5b6022
--- /dev/null
+++ b/guides/getting-started.md
@@ -0,0 +1,68 @@
+# Getting Started
+
+## Install
+
+Welcome!
+
+Temple is a HTML DSL for Elixir, let's get started!
+
+
+First, make sure you are using Elixir `V1.13` or higher.
+
+Add `:temple` to your deps and run `mix deps.get`
+
+```elixir
+{:temple, "~> 0.9.0-rc.0"}
+```
+
+Now you must prepend the Temple compiler to your projects `:compilers` configuration in `mix.exs`. There is a chance that your project doesn't set this option at all, but don't worry, it's really easy to add!
+
+```elixir
+defmodule MyApp.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ # ...
+ compilers: [:temple] ++ Mix.compilers(),
+ # ...
+ ]
+ end
+
+# ...
+
+end
+```
+
+All done, Now let's start building our app!
+
+## Configuration
+
+Temple works out of the box without any configuration, but here are a couple of conifg options that you could need to use.
+
+### Engine
+
+By default, Temple uses the built in `EEx.SmartEngine`. If you want to use a different engine, this is as easy as setting the `:engine` configuration option.
+
+```elixir
+# config/config.exs
+
+config :temple,
+ engine: Phoenix.HTML.Engine
+```
+
+### Aliases
+
+Temple code will reserve some local function calls for HTML tags. If you have a local function that you would like to use instead, you can create an alias for any tag.
+
+Common aliases for Phoenix projects look like this:
+
+```elixir
+config :temple,
+ aliases: [
+ label: :label_tag,
+ link: :link_tag,
+ select: :select_tag,
+ textarea: :textarea_tag
+ ]
+```
diff --git a/guides/migrating/0.8-to-0.9.md b/guides/migrating/0.8-to-0.9.md
new file mode 100644
index 0000000..a67b126
--- /dev/null
+++ b/guides/migrating/0.8-to-0.9.md
@@ -0,0 +1,3 @@
+# Migrating from 0.8 to 0.9
+
+TODO: explain it
diff --git a/guides/your-first-template.md b/guides/your-first-template.md
new file mode 100644
index 0000000..e2bd592
--- /dev/null
+++ b/guides/your-first-template.md
@@ -0,0 +1,236 @@
+# Your First Template
+
+A Temple template is written inside of the `Temple.temple/1` macro. Code inside there will be compiled into efficient Elixir code by the configured EEx engine.
+
+Local functions that have a corresponding HTML5 tag are reserved and will be used when generated your markup. Let's take a look at a basic form written with Temple.
+
+```elixir
+defmodule MyApp.FormExample do
+ import Temple
+
+ def form_page() do
+ assigns = %{title: "My Site | Sign Up", logged_in: false}
+
+ temple do
+ ""
+
+ html do
+ head do
+ meta charset: "utf-8"
+ meta http_equiv: "X-UA-Compatible", content: "IE=edge"
+ meta name: "viewport", content: "width=device-width, initial-scale=1.0"
+ link rel: "stylesheet", href: "/css/app.css"
+
+ title do: @title
+ end
+
+ body do
+ if @logged_in do
+ header class: "header" do
+ ul do
+ li do
+ a href: "/", do: "Home"
+ end
+ li do
+ a href: "/logout", do: "Logout"
+ end
+ end
+ end
+ end
+
+ form action: "", method: "get", class: "form-example" do
+ div class: "form-example" do
+ label for: "name", do: "Enter your name:"
+ input type: "text", name: "name", id: "name", required: true
+ end
+ div class: "form-example" do
+ label for: "email", do: "Enter your email:"
+ input type: "email", name: "email", id: "email", required: true
+ end
+ div class: "form-example" do
+ input type: "submit", value: "Subscribe!"
+ end
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+This example showcases an entire HTML page made with Temple! Let's dive a little deeper everything we're seeing here.
+
+Through out this guide, you will see code that includes features that are explained later on. Feel free to skip ahead to read on, or just keep reading. It will all make sense eventually!
+
+## Text Nodes
+
+The text node is a basic building block of any HTML document. In Temple, text nodes are represented by Elixir string literals.
+
+The very first line of the previous example is our doc type, emitted into the final document with `""`. This is a text node and will be emitted into the document as-is.
+
+Note: String _literals_ are emitted into text nodes. If you are using string interpolation with the `#{some_expression}` syntax, that is treated as an expression and will be evaluated in whichever way the configured engine evaluates expression. By default, the `EEx.SmartEngine` doesn't do any escaping of expressions, so that could still be emitted as-is, or even as HTML to be interpreted by your web browser.
+
+## Void Tags
+
+Void tags are HTML5 tags that do not have children, meaning they are "self closing".
+
+We can observe these in the previous example as the `` tag. You'll note that the tag does not have a `:do` key or a `do` block.
+
+## Non-void Tags
+
+Non-void tags are HTML5 tags that _do_ have children. You are probably most familiar with these type of tags, as they include the famous `
` and ``.
+
+These tags can enclose their children nodes with either a `do/end` block or the inline `:do` keyword.
+
+### Whitespace
+
+Nonvoid tags that use the `do/end` syntax will be emitted _with_ internal whitespace.
+
+```elixir
+temple do
+ div class: "foo" do
+ # children
+ end
+end
+```
+
+...will emit markup that looks like...
+
+```html
+
+
+
+```
+
+Note: The Elixir comment _will not_ be rendered into an HTML comment. This is just used in the example. (This does sound like a good feature though...)
+
+Nonvoid tags that use the `:do` keyword syntax will be emitted _without_ internal whitespace. This allows you to correctly use the `:empty` CSS psuedo-selector in your stylesheet.
+
+
+```elixir
+temple do
+ p class: "alert alert-info", do: "Your account was recently updated!"
+end
+```
+
+...will emit markup that looks like...
+
+```html
+
Your account was recently updated!
+```
+
+## Attributes
+
+Attributes are declared as a keyword list.
+
+- Keys with underscores are converted to the kebab syntax.
+- Values can be Elixir expressions.
+- Values that are compile time `true` will be emitted as a boolean attribute. `disabled` and `checked` are examples of boolean attributes.
+- Values that are compile time `false` will not be emitted into the document at all.
+- The class attribute has a special "object syntax" that allows you to specify classes as a keyword list, only emitting classes that evaluate to true into the final class.
+
+Let's look at an example.
+
+```elixir
+assigns = %{highlight?: false, user_name: "Mitch"}
+
+temple do
+ div id: "hero" do
+ h2 class: "font-bold", do: "Profile"
+
+ section data_controller: "hero" do
+ p class: ["border": @highlight?] do
+ "Name: #{@user_name}"
+ end
+
+ video autoplay: true, src: "https://example.com/rick-rolled.mp4"
+ end
+ end
+end
+```
+
+...will emit markup that looks like...
+
+```html
+
+
Profile
+
+
+
+ Name: Mitch
+
+
+
+
+
+```
+
+## Elixir Expressions
+
+### They Just Work
+
+Any Elixir expression can be used anywhere inside of a Temple template. Here are a few examples.
+
+```elixir
+temple do
+ h2 do: "Members"
+
+ ul do
+ for member <- @members do
+ li do: member
+ end
+ end
+end
+```
+
+### Match Expressions
+
+Match expression are handled slightly differently. Generally if you are assigning an expression to a variable (a match), you are going to use that binding later and do _not_ want to emit it into the document.
+
+So, match expressions are _not_ emitted into the document. They are functionally equivalent to the `<% .. %.` syntax of `EEx`. The expression is evaluated, but not included in the rendered document.
+
+Typically you should not be writing this type of expression inside of your template, but if you wanted to declare an alias, you would need to write the following to not emit the alias into the document.
+
+```elixir
+temple do
+ _ = alias My.Deep.Module
+
+ div do
+ Module.func()
+ end
+end
+```
+
+## Assigns
+
+Since Temple uses the `EEx.SmartEngine` by default, you are able to use the assigns feature.
+
+The assigns feature allows you to ergonomically access the members of a `assigns` variable by the `@` macro.
+
+The assign variable just needs to exist within the scope of the template (the same as a normal `EEx` template that uses `EEx.SmartEngine`), it can be a function parameter or created inside the function.
+
+```elixir
+def card(assigns) do
+ temple do
+ div class: "card" do
+ section class: "card-header" do
+ @name
+ end
+
+ section class: "card-body" do
+ @bio
+ end
+
+ if Enum.any?(@socials) do
+ section class: "card-footer" do
+ for social <- @socials do
+ a href: social.link do
+ social.name
+ end
+ end
+ end
+ end
+ end
+ end
+end
+```
diff --git a/integration_test/temple_demo/.formatter.exs b/integration_test/temple_demo/.formatter.exs
deleted file mode 100644
index 09a57c5..0000000
--- a/integration_test/temple_demo/.formatter.exs
+++ /dev/null
@@ -1,5 +0,0 @@
-[
- import_deps: [:ecto, :phoenix, :temple],
- inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
- subdirectories: ["priv/*/migrations"]
-]
diff --git a/integration_test/temple_demo/.tool-versions b/integration_test/temple_demo/.tool-versions
deleted file mode 100644
index 8d7a08d..0000000
--- a/integration_test/temple_demo/.tool-versions
+++ /dev/null
@@ -1 +0,0 @@
-elixir 1.9.4
diff --git a/integration_test/temple_demo/README.md b/integration_test/temple_demo/README.md
deleted file mode 100644
index 0f76960..0000000
--- a/integration_test/temple_demo/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# TempleDemo
-
-To start your Phoenix server:
-
- * Setup the project with `mix setup`
- * Start Phoenix endpoint with `mix phx.server`
-
-Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
-
-Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
-
-## Learn more
-
- * Official website: https://www.phoenixframework.org/
- * Guides: https://hexdocs.pm/phoenix/overview.html
- * Docs: https://hexdocs.pm/phoenix
- * Forum: https://elixirforum.com/c/phoenix-forum
- * Source: https://github.com/phoenixframework/phoenix
diff --git a/integration_test/temple_demo/config/config.exs b/integration_test/temple_demo/config/config.exs
deleted file mode 100644
index 9dc97c5..0000000
--- a/integration_test/temple_demo/config/config.exs
+++ /dev/null
@@ -1,43 +0,0 @@
-# This file is responsible for configuring your application
-# and its dependencies with the aid of the Mix.Config module.
-#
-# This configuration file is loaded before any dependency and
-# is restricted to this project.
-
-# General application configuration
-use Mix.Config
-
-config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
-
-config :temple_demo,
- ecto_repos: [TempleDemo.Repo]
-
-config :phoenix, :template_engines, exs: Temple.Engine
-
-# Configures the endpoint
-config :temple_demo, TempleDemoWeb.Endpoint,
- url: [host: "localhost"],
- secret_key_base: "ww1nKdikInNFHHUfSdCE1wiTcOmQq/KLvOxG7CY1TlKLDTmLW5yheCCYpfoxmZAW",
- render_errors: [view: TempleDemoWeb.ErrorView, accepts: ~w(html json), layout: false],
- pubsub_server: TempleDemo.PubSub,
- live_view: [signing_salt: "KCU/YIG0"]
-
-# Configures Elixir's Logger
-config :logger, :console,
- format: "$time $metadata[$level] $message\n",
- metadata: [:request_id]
-
-# Use Jason for JSON parsing in Phoenix
-config :phoenix, :json_library, Jason
-
-config :temple,
- mode: :normal,
- aliases: [
- label: :_label,
- link: :_link,
- textarea: :_textarea
- ]
-
-# Import environment specific config. This must remain at the bottom
-# of this file so it overrides the configuration defined above.
-import_config "#{Mix.env()}.exs"
diff --git a/integration_test/temple_demo/config/dev.exs b/integration_test/temple_demo/config/dev.exs
deleted file mode 100644
index abbf5d3..0000000
--- a/integration_test/temple_demo/config/dev.exs
+++ /dev/null
@@ -1,68 +0,0 @@
-use Mix.Config
-
-# Configure your database
-config :temple_demo, TempleDemo.Repo,
- username: "postgres",
- password: "postgres",
- database: "temple_demo_dev",
- hostname: "localhost",
- show_sensitive_data_on_connection_error: true,
- pool_size: 10
-
-# For development, we disable any cache and enable
-# debugging and code reloading.
-#
-# The watchers configuration can be used to run external
-# watchers to your application. For example, we use it
-# with webpack to recompile .js and .css sources.
-config :temple_demo, TempleDemoWeb.Endpoint,
- http: [port: 4000],
- debug_errors: true,
- code_reloader: true,
- check_origin: false,
- watchers: []
-
-# ## SSL Support
-#
-# In order to use HTTPS in development, a self-signed
-# certificate can be generated by running the following
-# Mix task:
-#
-# mix phx.gen.cert
-#
-# Note that this task requires Erlang/OTP 20 or later.
-# Run `mix help phx.gen.cert` for more information.
-#
-# The `http:` config above can be replaced with:
-#
-# https: [
-# port: 4001,
-# cipher_suite: :strong,
-# keyfile: "priv/cert/selfsigned_key.pem",
-# certfile: "priv/cert/selfsigned.pem"
-# ],
-#
-# If desired, both `http:` and `https:` keys can be
-# configured to run both http and https servers on
-# different ports.
-
-# Watch static and templates for browser reloading.
-config :temple_demo, TempleDemoWeb.Endpoint,
- live_reload: [
- patterns: [
- ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
- ~r"priv/gettext/.*(po)$",
- ~r"lib/temple_demo_web/(live|views|components)/.*(ex)$",
- ~r"lib/temple_demo_web/templates/.*(eex|exs)$"
- ]
- ]
-
-# Do not include metadata nor timestamps in development logs
-config :logger, :console, format: "[$level] $message\n"
-
-# Set a higher stacktrace during development. Avoid configuring such
-# in production as building large stacktraces may be expensive.
-config :phoenix, :stacktrace_depth, 20
-
-# Initialize plugs at runtime for faster development compilation
-config :phoenix, :plug_init_mode, :runtime
diff --git a/integration_test/temple_demo/config/prod.exs b/integration_test/temple_demo/config/prod.exs
deleted file mode 100644
index de2626c..0000000
--- a/integration_test/temple_demo/config/prod.exs
+++ /dev/null
@@ -1,55 +0,0 @@
-use Mix.Config
-
-# For production, don't forget to configure the url host
-# to something meaningful, Phoenix uses this information
-# when generating URLs.
-#
-# Note we also include the path to a cache manifest
-# containing the digested version of static files. This
-# manifest is generated by the `mix phx.digest` task,
-# which you should run after static files are built and
-# before starting your production server.
-config :temple_demo, TempleDemoWeb.Endpoint,
- url: [host: "example.com", port: 80],
- cache_static_manifest: "priv/static/cache_manifest.json"
-
-# Do not print debug messages in production
-config :logger, level: :info
-
-# ## SSL Support
-#
-# To get SSL working, you will need to add the `https` key
-# to the previous section and set your `:url` port to 443:
-#
-# config :temple_demo, TempleDemoWeb.Endpoint,
-# ...
-# url: [host: "example.com", port: 443],
-# https: [
-# port: 443,
-# cipher_suite: :strong,
-# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
-# certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
-# transport_options: [socket_opts: [:inet6]]
-# ]
-#
-# The `cipher_suite` is set to `:strong` to support only the
-# latest and more secure SSL ciphers. This means old browsers
-# and clients may not be supported. You can set it to
-# `:compatible` for wider support.
-#
-# `:keyfile` and `:certfile` expect an absolute path to the key
-# and cert in disk or a relative path inside priv, for example
-# "priv/ssl/server.key". For all supported SSL configuration
-# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
-#
-# We also recommend setting `force_ssl` in your endpoint, ensuring
-# no data is ever sent via http, always redirecting to https:
-#
-# config :temple_demo, TempleDemoWeb.Endpoint,
-# force_ssl: [hsts: true]
-#
-# Check `Plug.SSL` for all available options in `force_ssl`.
-
-# Finally import the config/prod.secret.exs which loads secrets
-# and configuration from environment variables.
-import_config "prod.secret.exs"
diff --git a/integration_test/temple_demo/config/prod.secret.exs b/integration_test/temple_demo/config/prod.secret.exs
deleted file mode 100644
index 06dc174..0000000
--- a/integration_test/temple_demo/config/prod.secret.exs
+++ /dev/null
@@ -1,41 +0,0 @@
-# In this file, we load production configuration and secrets
-# from environment variables. You can also hardcode secrets,
-# although such is generally not recommended and you have to
-# remember to add this file to your .gitignore.
-use Mix.Config
-
-database_url =
- System.get_env("DATABASE_URL") ||
- raise """
- environment variable DATABASE_URL is missing.
- For example: ecto://USER:PASS@HOST/DATABASE
- """
-
-config :temple_demo, TempleDemo.Repo,
- # ssl: true,
- url: database_url,
- pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
-
-secret_key_base =
- System.get_env("SECRET_KEY_BASE") ||
- raise """
- environment variable SECRET_KEY_BASE is missing.
- You can generate one by calling: mix phx.gen.secret
- """
-
-config :temple_demo, TempleDemoWeb.Endpoint,
- http: [
- port: String.to_integer(System.get_env("PORT") || "4000"),
- transport_options: [socket_opts: [:inet6]]
- ],
- secret_key_base: secret_key_base
-
-# ## Using releases (Elixir v1.9+)
-#
-# If you are doing OTP releases, you need to instruct Phoenix
-# to start each relevant endpoint:
-#
-# config :temple_demo, TempleDemoWeb.Endpoint, server: true
-#
-# Then you can assemble a release by calling `mix release`.
-# See `mix help release` for more information.
diff --git a/integration_test/temple_demo/config/test.exs b/integration_test/temple_demo/config/test.exs
deleted file mode 100644
index e07ba5e..0000000
--- a/integration_test/temple_demo/config/test.exs
+++ /dev/null
@@ -1,33 +0,0 @@
-use Mix.Config
-
-# Configure your database
-#
-# The MIX_TEST_PARTITION environment variable can be used
-# to provide built-in test partitioning in CI environment.
-# Run `mix help test` for more information.
-config :temple_demo, TempleDemo.Repo,
- username: "postgres",
- password: "postgres",
- database: "temple_demo_test#{System.get_env("MIX_TEST_PARTITION")}",
- hostname: "localhost",
- pool: Ecto.Adapters.SQL.Sandbox
-
-# We don't run a server during test. If one is required,
-# you can enable the server option below.
-config :temple_demo, TempleDemoWeb.Endpoint,
- http: [port: 4002],
- server: true
-
-config :temple_demo, :sql_sandbox, true
-
-config :wallaby,
- chromedriver: [
- # headless: false,
- binary: System.get_env("CHROME_BROWSER")
- ],
- base_url: "http://localhost:4002",
- otp_app: :temple_demo,
- screenshot_on_failure: true
-
-# Print only warnings and errors during test
-config :logger, level: :warn
diff --git a/integration_test/temple_demo/lib/temple_demo.ex b/integration_test/temple_demo/lib/temple_demo.ex
deleted file mode 100644
index 6552333..0000000
--- a/integration_test/temple_demo/lib/temple_demo.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule TempleDemo do
- @moduledoc """
- TempleDemo keeps the contexts that define your domain
- and business logic.
-
- Contexts are also responsible for managing your data, regardless
- if it comes from the database, an external API or others.
- """
-end
diff --git a/integration_test/temple_demo/lib/temple_demo/application.ex b/integration_test/temple_demo/lib/temple_demo/application.ex
deleted file mode 100644
index 75194f7..0000000
--- a/integration_test/temple_demo/lib/temple_demo/application.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule TempleDemo.Application do
- # See https://hexdocs.pm/elixir/Application.html
- # for more information on OTP Applications
- @moduledoc false
-
- use Application
-
- def start(_type, _args) do
- children = [
- # Start the Ecto repository
- TempleDemo.Repo,
- # Start the Telemetry supervisor
- TempleDemoWeb.Telemetry,
- # Start the PubSub system
- {Phoenix.PubSub, name: TempleDemo.PubSub},
- # Start the Endpoint (http/https)
- TempleDemoWeb.Endpoint
- # Start a worker by calling: TempleDemo.Worker.start_link(arg)
- # {TempleDemo.Worker, arg}
- ]
-
- # See https://hexdocs.pm/elixir/Supervisor.html
- # for other strategies and supported options
- opts = [strategy: :one_for_one, name: TempleDemo.Supervisor]
- Supervisor.start_link(children, opts)
- end
-
- # Tell Phoenix to update the endpoint configuration
- # whenever the application is updated.
- def config_change(changed, _new, removed) do
- TempleDemoWeb.Endpoint.config_change(changed, removed)
- :ok
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo/blog.ex b/integration_test/temple_demo/lib/temple_demo/blog.ex
deleted file mode 100644
index dfc94b6..0000000
--- a/integration_test/temple_demo/lib/temple_demo/blog.ex
+++ /dev/null
@@ -1,104 +0,0 @@
-defmodule TempleDemo.Blog do
- @moduledoc """
- The Blog context.
- """
-
- import Ecto.Query, warn: false
- alias TempleDemo.Repo
-
- alias TempleDemo.Blog.Post
-
- @doc """
- Returns the list of posts.
-
- ## Examples
-
- iex> list_posts()
- [%Post{}, ...]
-
- """
- def list_posts do
- Repo.all(Post)
- end
-
- @doc """
- Gets a single post.
-
- Raises `Ecto.NoResultsError` if the Post does not exist.
-
- ## Examples
-
- iex> get_post!(123)
- %Post{}
-
- iex> get_post!(456)
- ** (Ecto.NoResultsError)
-
- """
- def get_post!(id), do: Repo.get!(Post, id)
-
- @doc """
- Creates a post.
-
- ## Examples
-
- iex> create_post(%{field: value})
- {:ok, %Post{}}
-
- iex> create_post(%{field: bad_value})
- {:error, %Ecto.Changeset{}}
-
- """
- def create_post(attrs \\ %{}) do
- %Post{}
- |> Post.changeset(attrs)
- |> Repo.insert()
- end
-
- @doc """
- Updates a post.
-
- ## Examples
-
- iex> update_post(post, %{field: new_value})
- {:ok, %Post{}}
-
- iex> update_post(post, %{field: bad_value})
- {:error, %Ecto.Changeset{}}
-
- """
- def update_post(%Post{} = post, attrs) do
- post
- |> Post.changeset(attrs)
- |> Repo.update()
- end
-
- @doc """
- Deletes a post.
-
- ## Examples
-
- iex> delete_post(post)
- {:ok, %Post{}}
-
- iex> delete_post(post)
- {:error, %Ecto.Changeset{}}
-
- """
- def delete_post(%Post{} = post) do
- Repo.delete(post)
- end
-
- @doc """
- Returns an `%Ecto.Changeset{}` for tracking post changes.
-
- ## Examples
-
- iex> change_post(post)
- %Ecto.Changeset{data: %Post{}}
-
- """
- def change_post(%Post{} = post, attrs \\ %{}) do
- Post.changeset(post, attrs)
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo/blog/post.ex b/integration_test/temple_demo/lib/temple_demo/blog/post.ex
deleted file mode 100644
index 6e85e82..0000000
--- a/integration_test/temple_demo/lib/temple_demo/blog/post.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule TempleDemo.Blog.Post do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "posts" do
- field :author, :string
- field :body, :string
- field :published_at, :naive_datetime
- field :title, :string
-
- timestamps()
- end
-
- @doc false
- def changeset(post, attrs) do
- post
- |> cast(attrs, [:title, :body, :published_at, :author])
- |> validate_required([:title, :body, :published_at, :author])
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo/repo.ex b/integration_test/temple_demo/lib/temple_demo/repo.ex
deleted file mode 100644
index fe372f1..0000000
--- a/integration_test/temple_demo/lib/temple_demo/repo.ex
+++ /dev/null
@@ -1,5 +0,0 @@
-defmodule TempleDemo.Repo do
- use Ecto.Repo,
- otp_app: :temple_demo,
- adapter: Ecto.Adapters.Postgres
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web.ex b/integration_test/temple_demo/lib/temple_demo_web.ex
deleted file mode 100644
index 0a66735..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web.ex
+++ /dev/null
@@ -1,85 +0,0 @@
-defmodule TempleDemoWeb do
- @moduledoc """
- The entrypoint for defining your web interface, such
- as controllers, views, channels and so on.
-
- This can be used in your application as:
-
- use TempleDemoWeb, :controller
- use TempleDemoWeb, :view
-
- The definitions below will be executed for every view,
- controller, etc, so keep them short and clean, focused
- on imports, uses and aliases.
-
- Do NOT define functions inside the quoted expressions
- below. Instead, define any helper function in modules
- and import those modules here.
- """
-
- def controller do
- quote do
- use Phoenix.Controller, namespace: TempleDemoWeb
-
- import Plug.Conn
- import TempleDemoWeb.Gettext
- alias TempleDemoWeb.Router.Helpers, as: Routes
- end
- end
-
- def view do
- quote do
- use Phoenix.View,
- root: "lib/temple_demo_web/templates",
- namespace: TempleDemoWeb
-
- # Import convenience functions from controllers
- import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
-
- import Temple.Component
- alias TempleDemoWeb.Component.Outer
- alias TempleDemoWeb.Component.Flash
- alias TempleDemoWeb.Component.Form
-
- # Include shared imports and aliases for views
- unquote(view_helpers())
- end
- end
-
- def router do
- quote do
- use Phoenix.Router
-
- import Plug.Conn
- import Phoenix.Controller
- end
- end
-
- def channel do
- quote do
- use Phoenix.Channel
- import TempleDemoWeb.Gettext
- end
- end
-
- defp view_helpers do
- quote do
- # Use all HTML functionality (forms, tags, etc)
- use Phoenix.HTML
-
- # Import basic rendering functionality (render, render_layout, etc)
- import Phoenix.View
-
- import TempleDemoWeb.ErrorHelpers
- import TempleDemoWeb.Gettext
- alias TempleDemoWeb.Router.Helpers, as: Routes
- end
- end
-
- @doc """
- When used, dispatch to the appropriate controller/view/etc.
- """
- defmacro __using__(which) when is_atom(which) do
- apply(__MODULE__, which, [])
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/channels/user_socket.ex b/integration_test/temple_demo/lib/temple_demo_web/channels/user_socket.ex
deleted file mode 100644
index 8e9d8ff..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/channels/user_socket.ex
+++ /dev/null
@@ -1,35 +0,0 @@
-defmodule TempleDemoWeb.UserSocket do
- use Phoenix.Socket
-
- ## Channels
- # channel "room:*", TempleDemoWeb.RoomChannel
-
- # Socket params are passed from the client and can
- # be used to verify and authenticate a user. After
- # verification, you can put default assigns into
- # the socket that will be set for all channels, ie
- #
- # {:ok, assign(socket, :user_id, verified_user_id)}
- #
- # To deny connection, return `:error`.
- #
- # See `Phoenix.Token` documentation for examples in
- # performing token verification on connect.
- @impl true
- def connect(_params, socket, _connect_info) do
- {:ok, socket}
- end
-
- # Socket id's are topics that allow you to identify all sockets for a given user:
- #
- # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
- #
- # Would allow you to broadcast a "disconnect" event and terminate
- # all active sockets and channels for a given user:
- #
- # TempleDemoWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
- #
- # Returning `nil` makes this socket anonymous.
- @impl true
- def id(_socket), do: nil
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/components/flash.ex b/integration_test/temple_demo/lib/temple_demo_web/components/flash.ex
deleted file mode 100644
index 2603ec1..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/components/flash.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule TempleDemoWeb.Component.Flash do
- import Temple.Component
-
- render do
- div class: "alert alert-#{@type}", style: "border: solid 5px pink" do
- slot :default
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/components/form.ex b/integration_test/temple_demo/lib/temple_demo_web/components/form.ex
deleted file mode 100644
index 0532241..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/components/form.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule TempleDemoWeb.Component.Form do
- import Temple.Component
-
- render do
- f = Phoenix.HTML.Form.form_for(@changeset, @action)
-
- f
-
- slot :f, f: f
-
- ""
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/components/inner.ex b/integration_test/temple_demo/lib/temple_demo_web/components/inner.ex
deleted file mode 100644
index c6ea49a..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/components/inner.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule TempleDemoWeb.Component.Inner do
- import Temple.Component
-
- render do
- div id: "inner", outer_id: @outer_id do
- slot :default
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/components/outer.ex b/integration_test/temple_demo/lib/temple_demo_web/components/outer.ex
deleted file mode 100644
index fb28c22..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/components/outer.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-defmodule TempleDemoWeb.Component.Outer do
- import Temple.Component
- alias TempleDemoWeb.Component.Inner
-
- render do
- c Inner, outer_id: "from-outer" do
- slot :default
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/controllers/page_controller.ex b/integration_test/temple_demo/lib/temple_demo_web/controllers/page_controller.ex
deleted file mode 100644
index fae9dbd..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/controllers/page_controller.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule TempleDemoWeb.PageController do
- use TempleDemoWeb, :controller
-
- def index(conn, params) do
- render(conn, "index.html", text: params["text"])
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/controllers/post_controller.ex b/integration_test/temple_demo/lib/temple_demo_web/controllers/post_controller.ex
deleted file mode 100644
index c93a80d..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/controllers/post_controller.ex
+++ /dev/null
@@ -1,63 +0,0 @@
-defmodule TempleDemoWeb.PostController do
- use TempleDemoWeb, :controller
-
- alias TempleDemo.Blog
- alias TempleDemo.Blog.Post
-
- def index(conn, _params) do
- posts = Blog.list_posts()
- render(conn, "index.html", posts: posts)
- end
-
- def new(conn, _params) do
- changeset = Blog.change_post(%Post{})
-
- render(conn, "new.html", changeset: changeset)
- end
-
- def create(conn, %{"post" => post_params}) do
- case Blog.create_post(post_params) do
- {:ok, post} ->
- conn
- |> put_flash(:info, "Post created successfully.")
- |> redirect(to: Routes.post_path(conn, :show, post))
-
- {:error, %Ecto.Changeset{} = changeset} ->
- render(conn, "new.html", changeset: changeset)
- end
- end
-
- def show(conn, %{"id" => id}) do
- post = Blog.get_post!(id)
- render(conn, "show.html", post: post)
- end
-
- def edit(conn, %{"id" => id}) do
- post = Blog.get_post!(id)
- changeset = Blog.change_post(post)
- render(conn, "edit.html", post: post, changeset: changeset)
- end
-
- def update(conn, %{"id" => id, "post" => post_params}) do
- post = Blog.get_post!(id)
-
- case Blog.update_post(post, post_params) do
- {:ok, post} ->
- conn
- |> put_flash(:info, "Post updated successfully.")
- |> redirect(to: Routes.post_path(conn, :show, post))
-
- {:error, %Ecto.Changeset{} = changeset} ->
- render(conn, "edit.html", post: post, changeset: changeset)
- end
- end
-
- def delete(conn, %{"id" => id}) do
- post = Blog.get_post!(id)
- {:ok, _post} = Blog.delete_post(post)
-
- conn
- |> put_flash(:info, "Post deleted successfully.")
- |> redirect(to: Routes.post_path(conn, :index))
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/endpoint.ex b/integration_test/temple_demo/lib/temple_demo_web/endpoint.ex
deleted file mode 100644
index a888b97..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/endpoint.ex
+++ /dev/null
@@ -1,58 +0,0 @@
-defmodule TempleDemoWeb.Endpoint do
- use Phoenix.Endpoint, otp_app: :temple_demo
-
- if Application.get_env(:temple_demo, :sql_sandbox) do
- plug Phoenix.Ecto.SQL.Sandbox
- end
-
- # The session will be stored in the cookie and signed,
- # this means its contents can be read but not tampered with.
- # Set :encryption_salt if you would also like to encrypt it.
- @session_options [
- store: :cookie,
- key: "_temple_demo_key",
- signing_salt: "p72rbvlQ"
- ]
-
- socket "/socket", TempleDemoWeb.UserSocket,
- websocket: true,
- longpoll: false
-
- socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
-
- # Serve at "/" the static files from "priv/static" directory.
- #
- # You should set gzip to true if you are running phx.digest
- # when deploying your static files in production.
- plug Plug.Static,
- at: "/",
- from: :temple_demo,
- gzip: false,
- only: ~w(css fonts images js favicon.ico robots.txt)
-
- # Code reloading can be explicitly enabled under the
- # :code_reloader configuration of your endpoint.
- if code_reloading? do
- socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
- plug Phoenix.LiveReloader
- plug Phoenix.CodeReloader
- plug Phoenix.Ecto.CheckRepoStatus, otp_app: :temple_demo
- end
-
- plug Phoenix.LiveDashboard.RequestLogger,
- param_key: "request_logger",
- cookie_key: "request_logger"
-
- plug Plug.RequestId
- plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
-
- plug Plug.Parsers,
- parsers: [:urlencoded, :multipart, :json],
- pass: ["*/*"],
- json_decoder: Phoenix.json_library()
-
- plug Plug.MethodOverride
- plug Plug.Head
- plug Plug.Session, @session_options
- plug TempleDemoWeb.Router
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/gettext.ex b/integration_test/temple_demo/lib/temple_demo_web/gettext.ex
deleted file mode 100644
index a0aca6f..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/gettext.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule TempleDemoWeb.Gettext do
- @moduledoc """
- A module providing Internationalization with a gettext-based API.
-
- By using [Gettext](https://hexdocs.pm/gettext),
- your module gains a set of macros for translations, for example:
-
- import TempleDemoWeb.Gettext
-
- # Simple translation
- gettext("Here is the string to translate")
-
- # Plural translation
- ngettext("Here is the string to translate",
- "Here are the strings to translate",
- 3)
-
- # Domain-based translation
- dgettext("errors", "Here is the error message to translate")
-
- See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
- """
- use Gettext, otp_app: :temple_demo
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/router.ex b/integration_test/temple_demo/lib/temple_demo_web/router.ex
deleted file mode 100644
index 3f3027b..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/router.ex
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule TempleDemoWeb.Router do
- use TempleDemoWeb, :router
-
- pipeline :browser do
- plug :accepts, ["html"]
- plug :fetch_session
- plug :fetch_flash
- plug :protect_from_forgery
- plug :put_secure_browser_headers
- end
-
- pipeline :api do
- plug :accepts, ["json"]
- end
-
- scope "/", TempleDemoWeb do
- pipe_through :browser
-
- get "/", PageController, :index
- resources "/posts", PostController
- end
-
- # Other scopes may use custom stacks.
- # scope "/api", TempleDemoWeb do
- # pipe_through :api
- # end
-
- # Enables LiveDashboard only for development
- #
- # If you want to use the LiveDashboard in production, you should put
- # it behind authentication and allow only admins to access it.
- # If your application does not have an admins-only section yet,
- # you can use Plug.BasicAuth to set up some basic authentication
- # as long as you are also using SSL (which you should anyway).
- if Mix.env() in [:dev, :test] do
- import Phoenix.LiveDashboard.Router
-
- scope "/" do
- pipe_through :browser
- live_dashboard "/dashboard", metrics: TempleDemoWeb.Telemetry
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/telemetry.ex b/integration_test/temple_demo/lib/temple_demo_web/telemetry.ex
deleted file mode 100644
index ed592c4..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/telemetry.ex
+++ /dev/null
@@ -1,53 +0,0 @@
-defmodule TempleDemoWeb.Telemetry do
- use Supervisor
- import Telemetry.Metrics
-
- def start_link(arg) do
- Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
- end
-
- @impl true
- def init(_arg) do
- children = [
- {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
- # Add reporters as children of your supervision tree.
- # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
- ]
-
- Supervisor.init(children, strategy: :one_for_one)
- end
-
- def metrics do
- [
- # Phoenix Metrics
- summary("phoenix.endpoint.stop.duration",
- unit: {:native, :millisecond}
- ),
- summary("phoenix.router_dispatch.stop.duration",
- tags: [:route],
- unit: {:native, :millisecond}
- ),
-
- # Database Metrics
- summary("temple_demo.repo.query.total_time", unit: {:native, :millisecond}),
- summary("temple_demo.repo.query.decode_time", unit: {:native, :millisecond}),
- summary("temple_demo.repo.query.query_time", unit: {:native, :millisecond}),
- summary("temple_demo.repo.query.queue_time", unit: {:native, :millisecond}),
- summary("temple_demo.repo.query.idle_time", unit: {:native, :millisecond}),
-
- # VM Metrics
- summary("vm.memory.total", unit: {:byte, :kilobyte}),
- summary("vm.total_run_queue_lengths.total"),
- summary("vm.total_run_queue_lengths.cpu"),
- summary("vm.total_run_queue_lengths.io")
- ]
- end
-
- defp periodic_measurements do
- [
- # A module, function and arguments to be invoked periodically.
- # This function must call :telemetry.execute/3 and a metric must be added above.
- # {TempleDemoWeb, :count_users, []}
- ]
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/layout/app.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/layout/app.html.exs
deleted file mode 100644
index 36ac5ac..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/layout/app.html.exs
+++ /dev/null
@@ -1,44 +0,0 @@
-html lang: "en" do
- head do
- meta charset: "utf-8"
- meta http_equiv: "X-UA-Compatible", content: "IE=edge"
- meta name: "viewport", content: "width=device-width, initial-scale=1.0"
- title do: "TempleDemo · Phoenix Framework"
-
- _link(rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css"))
- end
-
- body do
- header do
- section class: "container" do
- nav role: "navigation" do
- ul do
- li do
- a href: "https://hexdocs.pm/phoenix/overview.html" do
- "Get Started"
- end
- end
- end
- end
-
- a href: "http://phoenixframework.org/", class: "phx-logo" do
- img src: Routes.static_path(@conn, "/images/phoenix.png"),
- alt: "Phoenix Framework Logo"
- end
- end
- end
-
- main role: "main", class: "container" do
- for {type, message} <- get_flash(@conn) do
- p class: "alert alert-#{type}", role: "alert" do
- message
- end
- end
-
- @inner_content
- end
-
- script type: "text/javascript", src: Routes.static_path(@conn, "/js/phoenix_html.js")
- script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/page/index.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/page/index.html.exs
deleted file mode 100644
index 0657fb4..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/page/index.html.exs
+++ /dev/null
@@ -1,77 +0,0 @@
-section class: "phx-hero" do
- h1 do
- gettext("Welcome to %{name}!", name: "Phoenix")
- end
-
- c Outer, outer_id: "hello" do
- "inner content of outer"
- end
-
- case @text do
- "staging" ->
- p do
- "Peace-of-mind from prototype to staging"
- end
-
- _ ->
- p do
- "Peace-of-mind from prototype to production"
- end
- end
-end
-
-section class: "row" do
- article class: "column" do
- h2 do: "Resources"
-
- ul do
- li do
- a href: "https://hexdocs.pm/phoenix/overview.html" do
- "Guides & Docs"
- end
- end
-
- li do
- a href: "https://github.com/phoenixframework/phoenix" do
- "Source"
- end
- end
-
- li do
- a href: "https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md" do
- "v1.5 Changelog"
- end
- end
- end
- end
-
- article class: "column" do
- h2 do: "Help"
-
- ul do
- li do
- a href: "https://elixirforum.com/c/phoenix-forum" do
- "Forum"
- end
- end
-
- li do
- a href: "https://webchat.freenode.net/?channels=elixir-lang" do
- "#elixir-lang on Freenode IRC"
- end
- end
-
- li do
- a href: "https://twitter.com/elixirphoenix" do
- "Twitter @elixirphoenix"
- end
- end
-
- li do
- a href: "https://elixir-slackin.herokuapp.com/" do
- "Elixir on Slack"
- end
- end
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/edit.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/edit.html.exs
deleted file mode 100644
index 16e842f..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/edit.html.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-h1 do: "Edit Post"
-
-render("form.html", Map.put(assigns, :action, Routes.post_path(@conn, :update, @post)))
-
-span do
- link "Back", to: Routes.post_path(@conn, :index)
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs
deleted file mode 100644
index aaad6e6..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs
+++ /dev/null
@@ -1,31 +0,0 @@
-c Form, changeset: @changeset, action: @action do
- slot :f, %{f: f} do
- if @changeset.action do
- c Flash, type: :info do
- p do: "Oops, something went wrong! Please check the errors below."
- end
- end
-
- label f, :title
- text_input f, :title
- error_tag(f, :title)
-
- label f, :body
- textarea f, :body
- error_tag(f, :body)
-
- label f, :published_at
- datetime_select f, :published_at
- error_tag(f, :published_at)
-
- label f, :author
- text_input f, :author
- error_tag(f, :author)
-
- input type: "text", disabled: true, id: "disabled-input"
-
- div do
- submit "Save"
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/index.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/index.html.exs
deleted file mode 100644
index 40d5281..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/index.html.exs
+++ /dev/null
@@ -1,36 +0,0 @@
-h1 do: "Listing Posts"
-
-table do
- c Headers do
- th do: "Title"
- th do: "Body"
- th do: "Published at"
- th do: "Author"
- th do: "BOB"
- end
-
- tbody do
- for post <- @posts do
- tr do
- td do: post.title
- td do: post.body
- td do: post.published_at
- td do: post.author
-
- td do
- link "Show", to: Routes.post_path(@conn, :show, post)
- link "Edit", to: Routes.post_path(@conn, :edit, post)
-
- link "Delete",
- to: Routes.post_path(@conn, :delete, post),
- method: :delete,
- data: [confirm: "Are you sure?"]
- end
- end
- end
- end
-end
-
-span do
- link "New Post", to: Routes.post_path(@conn, :new)
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/new.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/new.html.exs
deleted file mode 100644
index ce06483..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/new.html.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-h1 do: "New Post"
-
-render("form.html", Map.put(assigns, :action, Routes.post_path(@conn, :create)))
-
-span do
- link "Back", to: Routes.post_path(@conn, :index)
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/show.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/show.html.exs
deleted file mode 100644
index 200882f..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/show.html.exs
+++ /dev/null
@@ -1,28 +0,0 @@
-h1 do: "Show Post"
-
-ul do
- li do: [strong(do: "Title"), @post.title]
-
- li do
- strong do: "Body"
- Phoenix.HTML.Format.text_to_html(@post.body, attributes: [class: "whitespace-pre"])
- end
-
- li do
- strong do: "Published at"
- @post.published_at
- end
-
- li do
- strong do: "Author"
- @post.author
- end
-
- span do
- link "Edit", to: Routes.post_path(@conn, :edit, @post)
- end
-
- span do
- link "Back", to: Routes.post_path(@conn, :index)
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/views/error_helpers.ex b/integration_test/temple_demo/lib/temple_demo_web/views/error_helpers.ex
deleted file mode 100644
index dc48246..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/views/error_helpers.ex
+++ /dev/null
@@ -1,47 +0,0 @@
-defmodule TempleDemoWeb.ErrorHelpers do
- @moduledoc """
- Conveniences for translating and building error messages.
- """
-
- use Phoenix.HTML
-
- @doc """
- Generates tag for inlined form input errors.
- """
- def error_tag(form, field) do
- Enum.map(Keyword.get_values(form.errors, field), fn error ->
- content_tag(:span, translate_error(error),
- class: "invalid-feedback",
- phx_feedback_for: input_id(form, field)
- )
- end)
- end
-
- @doc """
- Translates an error message using gettext.
- """
- def translate_error({msg, opts}) do
- # When using gettext, we typically pass the strings we want
- # to translate as a static argument:
- #
- # # Translate "is invalid" in the "errors" domain
- # dgettext("errors", "is invalid")
- #
- # # Translate the number of files with plural rules
- # dngettext("errors", "1 file", "%{count} files", count)
- #
- # Because the error messages we show in our forms and APIs
- # are defined inside Ecto, we need to translate them dynamically.
- # This requires us to call the Gettext module passing our gettext
- # backend as first argument.
- #
- # Note we use the "errors" domain, which means translations
- # should be written to the errors.po file. The :count option is
- # set by Ecto and indicates we should also apply plural rules.
- if count = opts[:count] do
- Gettext.dngettext(TempleDemoWeb.Gettext, "errors", msg, msg, count, opts)
- else
- Gettext.dgettext(TempleDemoWeb.Gettext, "errors", msg, opts)
- end
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/views/error_view.ex b/integration_test/temple_demo/lib/temple_demo_web/views/error_view.ex
deleted file mode 100644
index 31bd467..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/views/error_view.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule TempleDemoWeb.ErrorView do
- use TempleDemoWeb, :view
-
- # If you want to customize a particular status code
- # for a certain format, you may uncomment below.
- # def render("500.html", _assigns) do
- # "Internal Server Error"
- # end
-
- # By default, Phoenix returns the status message from
- # the template name. For example, "404.html" becomes
- # "Not Found".
- def template_not_found(template, _assigns) do
- Phoenix.Controller.status_message_from_template(template)
- end
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/views/layout_view.ex b/integration_test/temple_demo/lib/temple_demo_web/views/layout_view.ex
deleted file mode 100644
index 63b7f02..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/views/layout_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule TempleDemoWeb.LayoutView do
- use TempleDemoWeb, :view
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/views/page_view.ex b/integration_test/temple_demo/lib/temple_demo_web/views/page_view.ex
deleted file mode 100644
index 4194599..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/views/page_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule TempleDemoWeb.PageView do
- use TempleDemoWeb, :view
-end
diff --git a/integration_test/temple_demo/lib/temple_demo_web/views/post_view.ex b/integration_test/temple_demo/lib/temple_demo_web/views/post_view.ex
deleted file mode 100644
index 3696e25..0000000
--- a/integration_test/temple_demo/lib/temple_demo_web/views/post_view.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule TempleDemoWeb.PostView do
- use TempleDemoWeb, :view
-
- def thing(), do: "foobar"
-
- defcomp Headers do
- thead id: PostView.thing() do
- tr do
- slot :default
- end
- end
- end
-end
diff --git a/integration_test/temple_demo/mix.exs b/integration_test/temple_demo/mix.exs
deleted file mode 100644
index 751176d..0000000
--- a/integration_test/temple_demo/mix.exs
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule TempleDemo.MixProject do
- use Mix.Project
-
- def project do
- [
- app: :temple_demo,
- version: "0.1.0",
- elixir: "~> 1.7",
- elixirc_paths: elixirc_paths(Mix.env()),
- compilers: [:phoenix, :gettext] ++ Mix.compilers(),
- start_permanent: Mix.env() == :prod,
- aliases: aliases(),
- deps: deps()
- ]
- end
-
- # Configuration for the OTP application.
- #
- # Type `mix help compile.app` for more information.
- def application do
- [
- mod: {TempleDemo.Application, []},
- extra_applications: [:logger, :runtime_tools]
- ]
- end
-
- # Specifies which paths to compile per environment.
- defp elixirc_paths(:test), do: ["lib", "test/support"]
- defp elixirc_paths(_), do: ["lib"]
-
- # Specifies your project dependencies.
- #
- # Type `mix help deps` for examples and options.
- defp deps do
- [
- {:phoenix, "~> 1.5.1"},
- {:phoenix_ecto, "~> 4.1"},
- {:ecto_sql, "~> 3.4"},
- {:postgrex, ">= 0.0.0"},
- {:phoenix_html, "~> 2.11"},
- {:phoenix_live_reload, "~> 1.2", only: :dev},
- {:phoenix_live_dashboard, "~> 0.2.0"},
- {:telemetry_metrics, "~> 0.4"},
- {:telemetry_poller, "~> 0.4"},
- {:gettext, "~> 0.11"},
- {:jason, "~> 1.0"},
- {:plug_cowboy, "~> 2.0"},
- {:wallaby, "~> 0.28.0", only: :test},
- {:tzdata, "~> 1.0.3"},
- {:temple, path: "../../"}
- ]
- end
-
- # Aliases are shortcuts or tasks specific to the current project.
- # For example, to install project dependencies and perform other setup tasks, run:
- #
- # $ mix setup
- #
- # See the documentation for `Mix` for more info on aliases.
- defp aliases do
- [
- setup: ["deps.get", "ecto.setup"],
- "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
- "ecto.reset": ["ecto.drop", "ecto.setup"],
- test: ["ecto.create --quiet", "ecto.migrate", "test"]
- ]
- end
-end
diff --git a/integration_test/temple_demo/mix.lock b/integration_test/temple_demo/mix.lock
deleted file mode 100644
index 16d581d..0000000
--- a/integration_test/temple_demo/mix.lock
+++ /dev/null
@@ -1,44 +0,0 @@
-%{
- "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.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
- "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
- "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
- "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
- "db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
- "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
- "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.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.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
- "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"},
- "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"},
- "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"},
- "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
- "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
- "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
- "phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [: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", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"},
- "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.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_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_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
- "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
- "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [: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", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.5.0", "51c998f788c4e68fc9f947a5eba8c215fbb1d63a520f7604134cab0270ea6513", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b2c8925a5e2587446f33810a58c01e66b3c345652eeec809b76ba007acde71a"},
- "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
- "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
- "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"},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
- "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
- "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"},
- "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"},
- "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
- "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"},
-}
diff --git a/integration_test/temple_demo/priv/gettext/en/LC_MESSAGES/errors.po b/integration_test/temple_demo/priv/gettext/en/LC_MESSAGES/errors.po
deleted file mode 100644
index a589998..0000000
--- a/integration_test/temple_demo/priv/gettext/en/LC_MESSAGES/errors.po
+++ /dev/null
@@ -1,97 +0,0 @@
-## `msgid`s in this file come from POT (.pot) files.
-##
-## Do not add, change, or remove `msgid`s manually here as
-## they're tied to the ones in the corresponding POT file
-## (with the same domain).
-##
-## Use `mix gettext.extract --merge` or `mix gettext.merge`
-## to merge POT files into PO files.
-msgid ""
-msgstr ""
-"Language: en\n"
-
-## From Ecto.Changeset.cast/4
-msgid "can't be blank"
-msgstr ""
-
-## From Ecto.Changeset.unique_constraint/3
-msgid "has already been taken"
-msgstr ""
-
-## From Ecto.Changeset.put_change/3
-msgid "is invalid"
-msgstr ""
-
-## From Ecto.Changeset.validate_acceptance/3
-msgid "must be accepted"
-msgstr ""
-
-## From Ecto.Changeset.validate_format/3
-msgid "has invalid format"
-msgstr ""
-
-## From Ecto.Changeset.validate_subset/3
-msgid "has an invalid entry"
-msgstr ""
-
-## From Ecto.Changeset.validate_exclusion/3
-msgid "is reserved"
-msgstr ""
-
-## From Ecto.Changeset.validate_confirmation/3
-msgid "does not match confirmation"
-msgstr ""
-
-## From Ecto.Changeset.no_assoc_constraint/3
-msgid "is still associated with this entry"
-msgstr ""
-
-msgid "are still associated with this entry"
-msgstr ""
-
-## From Ecto.Changeset.validate_length/3
-msgid "should be %{count} character(s)"
-msgid_plural "should be %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have %{count} item(s)"
-msgid_plural "should have %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should be at least %{count} character(s)"
-msgid_plural "should be at least %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have at least %{count} item(s)"
-msgid_plural "should have at least %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should be at most %{count} character(s)"
-msgid_plural "should be at most %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have at most %{count} item(s)"
-msgid_plural "should have at most %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-## From Ecto.Changeset.validate_number/3
-msgid "must be less than %{number}"
-msgstr ""
-
-msgid "must be greater than %{number}"
-msgstr ""
-
-msgid "must be less than or equal to %{number}"
-msgstr ""
-
-msgid "must be greater than or equal to %{number}"
-msgstr ""
-
-msgid "must be equal to %{number}"
-msgstr ""
diff --git a/integration_test/temple_demo/priv/gettext/errors.pot b/integration_test/temple_demo/priv/gettext/errors.pot
deleted file mode 100644
index 39a220b..0000000
--- a/integration_test/temple_demo/priv/gettext/errors.pot
+++ /dev/null
@@ -1,95 +0,0 @@
-## This is a PO Template file.
-##
-## `msgid`s here are often extracted from source code.
-## Add new translations manually only if they're dynamic
-## translations that can't be statically extracted.
-##
-## Run `mix gettext.extract` to bring this file up to
-## date. Leave `msgstr`s empty as changing them here has no
-## effect: edit them in PO (`.po`) files instead.
-
-## From Ecto.Changeset.cast/4
-msgid "can't be blank"
-msgstr ""
-
-## From Ecto.Changeset.unique_constraint/3
-msgid "has already been taken"
-msgstr ""
-
-## From Ecto.Changeset.put_change/3
-msgid "is invalid"
-msgstr ""
-
-## From Ecto.Changeset.validate_acceptance/3
-msgid "must be accepted"
-msgstr ""
-
-## From Ecto.Changeset.validate_format/3
-msgid "has invalid format"
-msgstr ""
-
-## From Ecto.Changeset.validate_subset/3
-msgid "has an invalid entry"
-msgstr ""
-
-## From Ecto.Changeset.validate_exclusion/3
-msgid "is reserved"
-msgstr ""
-
-## From Ecto.Changeset.validate_confirmation/3
-msgid "does not match confirmation"
-msgstr ""
-
-## From Ecto.Changeset.no_assoc_constraint/3
-msgid "is still associated with this entry"
-msgstr ""
-
-msgid "are still associated with this entry"
-msgstr ""
-
-## From Ecto.Changeset.validate_length/3
-msgid "should be %{count} character(s)"
-msgid_plural "should be %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have %{count} item(s)"
-msgid_plural "should have %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should be at least %{count} character(s)"
-msgid_plural "should be at least %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have at least %{count} item(s)"
-msgid_plural "should have at least %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should be at most %{count} character(s)"
-msgid_plural "should be at most %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "should have at most %{count} item(s)"
-msgid_plural "should have at most %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
-
-## From Ecto.Changeset.validate_number/3
-msgid "must be less than %{number}"
-msgstr ""
-
-msgid "must be greater than %{number}"
-msgstr ""
-
-msgid "must be less than or equal to %{number}"
-msgstr ""
-
-msgid "must be greater than or equal to %{number}"
-msgstr ""
-
-msgid "must be equal to %{number}"
-msgstr ""
diff --git a/integration_test/temple_demo/priv/repo/migrations/.formatter.exs b/integration_test/temple_demo/priv/repo/migrations/.formatter.exs
deleted file mode 100644
index 49f9151..0000000
--- a/integration_test/temple_demo/priv/repo/migrations/.formatter.exs
+++ /dev/null
@@ -1,4 +0,0 @@
-[
- import_deps: [:ecto_sql],
- inputs: ["*.exs"]
-]
diff --git a/integration_test/temple_demo/priv/repo/migrations/20200522023854_create_posts.exs b/integration_test/temple_demo/priv/repo/migrations/20200522023854_create_posts.exs
deleted file mode 100644
index 18ac3da..0000000
--- a/integration_test/temple_demo/priv/repo/migrations/20200522023854_create_posts.exs
+++ /dev/null
@@ -1,14 +0,0 @@
-defmodule TempleDemo.Repo.Migrations.CreatePosts do
- use Ecto.Migration
-
- def change do
- create table(:posts) do
- add :title, :string
- add :body, :text
- add :published_at, :naive_datetime
- add :author, :string
-
- timestamps()
- end
- end
-end
diff --git a/integration_test/temple_demo/priv/repo/seeds.exs b/integration_test/temple_demo/priv/repo/seeds.exs
deleted file mode 100644
index 04ba8d9..0000000
--- a/integration_test/temple_demo/priv/repo/seeds.exs
+++ /dev/null
@@ -1,11 +0,0 @@
-# Script for populating the database. You can run it as:
-#
-# mix run priv/repo/seeds.exs
-#
-# Inside the script, you can read and write to any of your
-# repositories directly:
-#
-# TempleDemo.Repo.insert!(%TempleDemo.SomeSchema{})
-#
-# We recommend using the bang functions (`insert!`, `update!`
-# and so on) as they will fail if something goes wrong.
diff --git a/integration_test/temple_demo/priv/static/css/app.css b/integration_test/temple_demo/priv/static/css/app.css
deleted file mode 100644
index 416c782..0000000
--- a/integration_test/temple_demo/priv/static/css/app.css
+++ /dev/null
@@ -1,40 +0,0 @@
-/* This file is for your main application css. */
-@import "./phoenix.css";
-
-.invalid-feedback {
- color: red;
-}
-
-.alert:empty {
- display: none;
-}
-
-.alert {
- padding: 15px;
- margin-bottom: 20px;
- border: 1px solid transparent;
- border-radius: 4px;
-}
-
-.alert-info {
- color: #31708f;
- background-color: #d9edf7;
- border-color: #bce8f1;
-}
-.alert-warning {
- color: #8a6d3b;
- background-color: #fcf8e3;
- border-color: #faebcc;
-}
-.alert-error {
- color: #a94442;
- background-color: #f2dede;
- border-color: #ebccd1;
-}
-.alert p {
- margin-bottom: 0;
-}
-
-strong {
- font-weight: 700;
-}
diff --git a/integration_test/temple_demo/priv/static/css/phoenix.css b/integration_test/temple_demo/priv/static/css/phoenix.css
deleted file mode 100644
index 3767b31..0000000
--- a/integration_test/temple_demo/priv/static/css/phoenix.css
+++ /dev/null
@@ -1,101 +0,0 @@
-/* Includes some default style for the starter application.
- * This can be safely deleted to start fresh.
- */
-
-/* Milligram v1.3.0 https://milligram.github.io
- * Copyright (c) 2017 CJ Patoilo Licensed under the MIT license
- */
-
-*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}
-
-/* General style */
-h1{font-size: 3.6rem; line-height: 1.25}
-h2{font-size: 2.8rem; line-height: 1.3}
-h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35}
-h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5}
-h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4}
-h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2}
-pre{padding: 1em;}
-
-.container{
- margin: 0 auto;
- max-width: 80.0rem;
- padding: 0 2.0rem;
- position: relative;
- width: 100%
-}
-select {
- width: auto;
-}
-
-/* Phoenix promo and logo */
-.phx-hero {
- text-align: center;
- border-bottom: 1px solid #e3e3e3;
- background: #eee;
- border-radius: 6px;
- padding: 3em 3em 1em;
- margin-bottom: 3rem;
- font-weight: 200;
- font-size: 120%;
-}
-.phx-hero input {
- background: #ffffff;
-}
-.phx-logo {
- min-width: 300px;
- margin: 1rem;
- display: block;
-}
-.phx-logo img {
- width: auto;
- display: block;
-}
-
-/* Headers */
-header {
- width: 100%;
- background: #fdfdfd;
- border-bottom: 1px solid #eaeaea;
- margin-bottom: 2rem;
-}
-header section {
- align-items: center;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
-header section :first-child {
- order: 2;
-}
-header section :last-child {
- order: 1;
-}
-header nav ul,
-header nav li {
- margin: 0;
- padding: 0;
- display: block;
- text-align: right;
- white-space: nowrap;
-}
-header nav ul {
- margin: 1rem;
- margin-top: 0;
-}
-header nav a {
- display: block;
-}
-
-@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */
- header section {
- flex-direction: row;
- }
- header nav ul {
- margin: 1rem;
- }
- .phx-logo {
- flex-basis: 527px;
- margin: 2rem 1rem;
- }
-}
diff --git a/integration_test/temple_demo/priv/static/favicon.ico b/integration_test/temple_demo/priv/static/favicon.ico
deleted file mode 100644
index 73de524..0000000
Binary files a/integration_test/temple_demo/priv/static/favicon.ico and /dev/null differ
diff --git a/integration_test/temple_demo/priv/static/images/phoenix.png b/integration_test/temple_demo/priv/static/images/phoenix.png
deleted file mode 100644
index 9c81075..0000000
Binary files a/integration_test/temple_demo/priv/static/images/phoenix.png and /dev/null differ
diff --git a/integration_test/temple_demo/priv/static/js/app.js b/integration_test/temple_demo/priv/static/js/app.js
deleted file mode 100644
index c0b39de..0000000
--- a/integration_test/temple_demo/priv/static/js/app.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// for phoenix_html support, including form and button helpers
-// copy the following scripts into your javascript bundle:
-// * deps/phoenix_html/priv/static/phoenix_html.js
diff --git a/integration_test/temple_demo/priv/static/js/phoenix.js b/integration_test/temple_demo/priv/static/js/phoenix.js
deleted file mode 100644
index 692f637..0000000
--- a/integration_test/temple_demo/priv/static/js/phoenix.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Phoenix=t():e.Phoenix=t()}(this,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){(function(t){e.exports=t.Phoenix=n(2)}).call(this,n(1))},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";function i(e){return function(e){if(Array.isArray(e))return a(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||s(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function r(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(e)))return;var n=[],i=!0,o=!1,r=void 0;try{for(var s,a=e[Symbol.iterator]();!(i=(s=a.next()).done)&&(n.push(s.value),!t||n.length!==t);i=!0);}catch(e){o=!0,r=e}finally{try{i||null==a.return||a.return()}finally{if(o)throw r}}return n}(e,t)||s(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function s(e,t){if(e){if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(n):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?a(e,t):void 0}}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:this.timeout;if(this.joinedOnce)throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");return this.timeout=e,this.joinedOnce=!0,this.rejoin(),this.joinPush}},{key:"onClose",value:function(e){this.on(R,e)}},{key:"onError",value:function(e){return this.on(S,(function(t){return e(t)}))}},{key:"on",value:function(e,t){var n=this.bindingRef++;return this.bindings.push({event:e,ref:n,callback:t}),n}},{key:"off",value:function(e,t){this.bindings=this.bindings.filter((function(n){return!(n.event===e&&(void 0===t||t===n.ref))}))}},{key:"canPush",value:function(){return this.socket.isConnected()&&this.isJoined()}},{key:"push",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.timeout;if(!this.joinedOnce)throw new Error("tried to push '".concat(e,"' to '").concat(this.topic,"' before joining. Use channel.join() before pushing events"));var i=new _(this,e,(function(){return t}),n);return this.canPush()?i.send():(i.startTimeout(),this.pushBuffer.push(i)),i}},{key:"leave",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.timeout;this.rejoinTimer.reset(),this.joinPush.cancelTimeout(),this.state=C;var n=function(){e.socket.hasLogger()&&e.socket.log("channel","leave ".concat(e.topic)),e.trigger(R,"leave")},i=new _(this,E,L({}),t);return i.receive("ok",(function(){return n()})).receive("timeout",(function(){return n()})),i.send(),this.canPush()||i.trigger("ok",{}),i}},{key:"onMessage",value:function(e,t,n){return t}},{key:"isLifecycleEvent",value:function(e){return x.indexOf(e)>=0}},{key:"isMember",value:function(e,t,n,i){return this.topic===e&&(!i||i===this.joinRef()||!this.isLifecycleEvent(t)||(this.socket.hasLogger()&&this.socket.log("channel","dropping outdated message",{topic:e,event:t,payload:n,joinRef:i}),!1))}},{key:"joinRef",value:function(){return this.joinPush.ref}},{key:"rejoin",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.timeout;this.isLeaving()||(this.socket.leaveOpenTopic(this.topic),this.state=j,this.joinPush.resend(e))}},{key:"trigger",value:function(e,t,n,i){var o=this.onMessage(e,t,n,i);if(t&&!o)throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");for(var r=this.bindings.filter((function(t){return t.event===e})),s=0;s1&&void 0!==arguments[1]?arguments[1]:{};c(this,e),this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.timeout=i.timeout||1e4,this.transport=i.transport||d.WebSocket||H,this.defaultEncoder=M.encode,this.defaultDecoder=M.decode,this.closeWasClean=!1,this.unloaded=!1,this.binaryType=i.binaryType||"arraybuffer",this.transport!==H?(this.encode=i.encode||this.defaultEncoder,this.decode=i.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder),f&&f.addEventListener&&f.addEventListener("unload",(function(e){n.conn&&(n.unloaded=!0,n.abnormalClose("unloaded"))})),this.heartbeatIntervalMs=i.heartbeatIntervalMs||3e4,this.rejoinAfterMs=function(e){return i.rejoinAfterMs?i.rejoinAfterMs(e):[1e3,2e3,5e3][e-1]||1e4},this.reconnectAfterMs=function(e){return n.unloaded?100:i.reconnectAfterMs?i.reconnectAfterMs(e):[10,50,100,150,200,250,500,1e3,2e3][e-1]||5e3},this.logger=i.logger||null,this.longpollerTimeout=i.longpollerTimeout||2e4,this.params=L(i.params||{}),this.endPoint="".concat(t,"/").concat(P),this.vsn=i.vsn||"2.0.0",this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new I((function(){n.teardown((function(){return n.connect()}))}),this.reconnectAfterMs)}return h(e,[{key:"protocol",value:function(){return location.protocol.match(/^https/)?"wss":"ws"}},{key:"endPointURL",value:function(){var e=D.appendParams(D.appendParams(this.endPoint,this.params()),{vsn:this.vsn});return"/"!==e.charAt(0)?e:"/"===e.charAt(1)?"".concat(this.protocol(),":").concat(e):"".concat(this.protocol(),"://").concat(location.host).concat(e)}},{key:"disconnect",value:function(e,t,n){this.closeWasClean=!0,this.reconnectTimer.reset(),this.teardown(e,t,n)}},{key:"connect",value:function(e){var t=this;e&&(console&&console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"),this.params=L(e)),this.conn||(this.closeWasClean=!1,this.conn=new this.transport(this.endPointURL()),this.conn.binaryType=this.binaryType,this.conn.timeout=this.longpollerTimeout,this.conn.onopen=function(){return t.onConnOpen()},this.conn.onerror=function(e){return t.onConnError(e)},this.conn.onmessage=function(e){return t.onConnMessage(e)},this.conn.onclose=function(e){return t.onConnClose(e)})}},{key:"log",value:function(e,t,n){this.logger(e,t,n)}},{key:"hasLogger",value:function(){return null!==this.logger}},{key:"onOpen",value:function(e){var t=this.makeRef();return this.stateChangeCallbacks.open.push([t,e]),t}},{key:"onClose",value:function(e){var t=this.makeRef();return this.stateChangeCallbacks.close.push([t,e]),t}},{key:"onError",value:function(e){var t=this.makeRef();return this.stateChangeCallbacks.error.push([t,e]),t}},{key:"onMessage",value:function(e){var t=this.makeRef();return this.stateChangeCallbacks.message.push([t,e]),t}},{key:"onConnOpen",value:function(){this.hasLogger()&&this.log("transport","connected to ".concat(this.endPointURL())),this.unloaded=!1,this.closeWasClean=!1,this.flushSendBuffer(),this.reconnectTimer.reset(),this.resetHeartbeat(),this.stateChangeCallbacks.open.forEach((function(e){return(0,r(e,2)[1])()}))}},{key:"resetHeartbeat",value:function(){var e=this;this.conn&&this.conn.skipHeartbeat||(this.pendingHeartbeatRef=null,clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval((function(){return e.sendHeartbeat()}),this.heartbeatIntervalMs))}},{key:"teardown",value:function(e,t,n){var i=this;if(!this.conn)return e&&e();this.waitForBufferDone((function(){i.conn&&(t?i.conn.close(t,n||""):i.conn.close()),i.waitForSocketClosed((function(){i.conn&&(i.conn.onclose=function(){},i.conn=null),e&&e()}))}))}},{key:"waitForBufferDone",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;5===n||!this.conn||this.conn.bufferedAmount&&0===this.conn.bufferedAmount?e():setTimeout((function(){t.waitForBufferDone(e,n+1)}),150*n)}},{key:"waitForSocketClosed",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;5!==n&&this.conn&&this.conn.readyState!==m?setTimeout((function(){t.waitForSocketClosed(e,n+1)}),150*n):e()}},{key:"onConnClose",value:function(e){this.hasLogger()&&this.log("transport","close",e),this.triggerChanError(),clearInterval(this.heartbeatTimer),this.closeWasClean||this.reconnectTimer.scheduleTimeout(),this.stateChangeCallbacks.close.forEach((function(t){return(0,r(t,2)[1])(e)}))}},{key:"onConnError",value:function(e){this.hasLogger()&&this.log("transport",e),this.triggerChanError(),this.stateChangeCallbacks.error.forEach((function(t){return(0,r(t,2)[1])(e)}))}},{key:"triggerChanError",value:function(){this.channels.forEach((function(e){e.isErrored()||e.isLeaving()||e.isClosed()||e.trigger(S)}))}},{key:"connectionState",value:function(){switch(this.conn&&this.conn.readyState){case p:return"connecting";case v:return"open";case y:return"closing";default:return"closed"}}},{key:"isConnected",value:function(){return"open"===this.connectionState()}},{key:"remove",value:function(e){this.off(e.stateChangeRefs),this.channels=this.channels.filter((function(t){return t.joinRef()!==e.joinRef()}))}},{key:"off",value:function(e){for(var t in this.stateChangeCallbacks)this.stateChangeCallbacks[t]=this.stateChangeCallbacks[t].filter((function(t){var n=r(t,1)[0];return-1===e.indexOf(n)}))}},{key:"channel",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=new A(e,t,this);return this.channels.push(n),n}},{key:"push",value:function(e){var t=this;if(this.hasLogger()){var n=e.topic,i=e.event,o=e.payload,r=e.ref,s=e.join_ref;this.log("push","".concat(n," ").concat(i," (").concat(s,", ").concat(r,")"),o)}this.isConnected()?this.encode(e,(function(e){return t.conn.send(e)})):this.sendBuffer.push((function(){return t.encode(e,(function(e){return t.conn.send(e)}))}))}},{key:"makeRef",value:function(){var e=this.ref+1;return e===this.ref?this.ref=0:this.ref=e,this.ref.toString()}},{key:"sendHeartbeat",value:function(){if(this.isConnected()){if(this.pendingHeartbeatRef)return this.pendingHeartbeatRef=null,this.hasLogger()&&this.log("transport","heartbeat timeout. Attempting to re-establish connection"),void this.abnormalClose("heartbeat timeout");this.pendingHeartbeatRef=this.makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef})}}},{key:"abnormalClose",value:function(e){this.closeWasClean=!1,this.conn.close(1e3,e)}},{key:"flushSendBuffer",value:function(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach((function(e){return e()})),this.sendBuffer=[])}},{key:"onConnMessage",value:function(e){var t=this;this.decode(e.data,(function(e){var n=e.topic,i=e.event,o=e.payload,s=e.ref,a=e.join_ref;s&&s===t.pendingHeartbeatRef&&(t.pendingHeartbeatRef=null),t.hasLogger()&&t.log("receive","".concat(o.status||""," ").concat(n," ").concat(i," ").concat(s&&"("+s+")"||""),o);for(var c=0;c1&&void 0!==arguments[1]?arguments[1]:{};c(this,e);var o=i.events||{state:"presence_state",diff:"presence_diff"};this.state={},this.pendingDiffs=[],this.channel=t,this.joinRef=null,this.caller={onJoin:function(){},onLeave:function(){},onSync:function(){}},this.channel.on(o.state,(function(t){var i=n.caller,o=i.onJoin,r=i.onLeave,s=i.onSync;n.joinRef=n.channel.joinRef(),n.state=e.syncState(n.state,t,o,r),n.pendingDiffs.forEach((function(t){n.state=e.syncDiff(n.state,t,o,r)})),n.pendingDiffs=[],s()})),this.channel.on(o.diff,(function(t){var i=n.caller,o=i.onJoin,r=i.onLeave,s=i.onSync;n.inPendingSyncState()?n.pendingDiffs.push(t):(n.state=e.syncDiff(n.state,t,o,r),s())}))}return h(e,[{key:"onJoin",value:function(e){this.caller.onJoin=e}},{key:"onLeave",value:function(e){this.caller.onLeave=e}},{key:"onSync",value:function(e){this.caller.onSync=e}},{key:"list",value:function(t){return e.list(this.state,t)}},{key:"inPendingSyncState",value:function(){return!this.joinRef||this.joinRef!==this.channel.joinRef()}}],[{key:"syncState",value:function(e,t,n,i){var o=this,r=this.clone(e),s={},a={};return this.map(r,(function(e,n){t[e]||(a[e]=n)})),this.map(t,(function(e,t){var n=r[e];if(n){var i=t.metas.map((function(e){return e.phx_ref})),c=n.metas.map((function(e){return e.phx_ref})),u=t.metas.filter((function(e){return c.indexOf(e.phx_ref)<0})),h=n.metas.filter((function(e){return i.indexOf(e.phx_ref)<0}));u.length>0&&(s[e]=t,s[e].metas=u),h.length>0&&(a[e]=o.clone(n),a[e].metas=h)}else s[e]=t})),this.syncDiff(r,{joins:s,leaves:a},n,i)}},{key:"syncDiff",value:function(e,t,n,o){var r=t.joins,s=t.leaves,a=this.clone(e);return n||(n=function(){}),o||(o=function(){}),this.map(r,(function(e,t){var o=a[e];if(a[e]=t,o){var r,s=a[e].metas.map((function(e){return e.phx_ref})),c=o.metas.filter((function(e){return s.indexOf(e.phx_ref)<0}));(r=a[e].metas).unshift.apply(r,i(c))}n(e,o,t)})),this.map(s,(function(e,t){var n=a[e];if(n){var i=t.metas.map((function(e){return e.phx_ref}));n.metas=n.metas.filter((function(e){return i.indexOf(e.phx_ref)<0})),o(e,n,t),0===n.metas.length&&delete a[e]}})),a}},{key:"list",value:function(e,t){return t||(t=function(e,t){return t}),this.map(e,(function(e,n){return t(e,n)}))}},{key:"map",value:function(e,t){return Object.getOwnPropertyNames(e).map((function(n){return t(n,e[n])}))}},{key:"clone",value:function(e){return JSON.parse(JSON.stringify(e))}}]),e}(),I=function(){function e(t,n){c(this,e),this.callback=t,this.timerCalc=n,this.timer=null,this.tries=0}return h(e,[{key:"reset",value:function(){this.tries=0,clearTimeout(this.timer)}},{key:"scheduleTimeout",value:function(){var e=this;clearTimeout(this.timer),this.timer=setTimeout((function(){e.tries=e.tries+1,e.callback()}),this.timerCalc(this.tries+1))}}]),e}()}])}));
\ No newline at end of file
diff --git a/integration_test/temple_demo/priv/static/js/phoenix_html.js b/integration_test/temple_demo/priv/static/js/phoenix_html.js
deleted file mode 100644
index e1ae852..0000000
--- a/integration_test/temple_demo/priv/static/js/phoenix_html.js
+++ /dev/null
@@ -1,76 +0,0 @@
-"use strict";
-
-(function() {
- var PolyfillEvent = eventConstructor();
-
- function eventConstructor() {
- if (typeof window.CustomEvent === "function") return window.CustomEvent;
- // IE<=9 Support
- function CustomEvent(event, params) {
- params = params || {bubbles: false, cancelable: false, detail: undefined};
- var evt = document.createEvent('CustomEvent');
- evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
- return evt;
- }
- CustomEvent.prototype = window.Event.prototype;
- return CustomEvent;
- }
-
- function buildHiddenInput(name, value) {
- var input = document.createElement("input");
- input.type = "hidden";
- input.name = name;
- input.value = value;
- return input;
- }
-
- function handleClick(element) {
- var to = element.getAttribute("data-to"),
- method = buildHiddenInput("_method", element.getAttribute("data-method")),
- csrf = buildHiddenInput("_csrf_token", element.getAttribute("data-csrf")),
- form = document.createElement("form"),
- target = element.getAttribute("target");
-
- form.method = (element.getAttribute("data-method") === "get") ? "get" : "post";
- form.action = to;
- form.style.display = "hidden";
-
- if (target) form.target = target;
-
- form.appendChild(csrf);
- form.appendChild(method);
- document.body.appendChild(form);
- form.submit();
- }
-
- window.addEventListener("click", function(e) {
- var element = e.target;
-
- while (element && element.getAttribute) {
- var phoenixLinkEvent = new PolyfillEvent('phoenix.link.click', {
- "bubbles": true, "cancelable": true
- });
-
- if (!element.dispatchEvent(phoenixLinkEvent)) {
- e.preventDefault();
- e.stopImmediatePropagation();
- return false;
- }
-
- if (element.getAttribute("data-method")) {
- handleClick(element);
- e.preventDefault();
- return false;
- } else {
- element = element.parentNode;
- }
- }
- }, false);
-
- window.addEventListener('phoenix.link.click', function (e) {
- var message = e.target.getAttribute("data-confirm");
- if(message && !window.confirm(message)) {
- e.preventDefault();
- }
- }, false);
-})();
diff --git a/integration_test/temple_demo/priv/static/robots.txt b/integration_test/temple_demo/priv/static/robots.txt
deleted file mode 100644
index 3c9c7c0..0000000
--- a/integration_test/temple_demo/priv/static/robots.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
-#
-# To ban all spiders from the entire site uncomment the next two lines:
-# User-agent: *
-# Disallow: /
diff --git a/integration_test/temple_demo/test/support/channel_case.ex b/integration_test/temple_demo/test/support/channel_case.ex
deleted file mode 100644
index 1cbae75..0000000
--- a/integration_test/temple_demo/test/support/channel_case.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule TempleDemoWeb.ChannelCase do
- @moduledoc """
- This module defines the test case to be used by
- channel tests.
-
- Such tests rely on `Phoenix.ChannelTest` and also
- import other functionality to make it easier
- to build common data structures and query the data layer.
-
- Finally, if the test case interacts with the database,
- we enable the SQL sandbox, so changes done to the database
- are reverted at the end of every test. If you are using
- PostgreSQL, you can even run database tests asynchronously
- by setting `use TempleDemoWeb.ChannelCase, async: true`, although
- this option is not recommended for other databases.
- """
-
- use ExUnit.CaseTemplate
-
- using do
- quote do
- # Import conveniences for testing with channels
- import Phoenix.ChannelTest
- import TempleDemoWeb.ChannelCase
-
- # The default endpoint for testing
- @endpoint TempleDemoWeb.Endpoint
- end
- end
-
- setup tags do
- :ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
-
- unless tags[:async] do
- Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
- end
-
- :ok
- end
-end
diff --git a/integration_test/temple_demo/test/support/conn_case.ex b/integration_test/temple_demo/test/support/conn_case.ex
deleted file mode 100644
index 8ce6ed3..0000000
--- a/integration_test/temple_demo/test/support/conn_case.ex
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule TempleDemoWeb.ConnCase do
- @moduledoc """
- This module defines the test case to be used by
- tests that require setting up a connection.
-
- Such tests rely on `Phoenix.ConnTest` and also
- import other functionality to make it easier
- to build common data structures and query the data layer.
-
- Finally, if the test case interacts with the database,
- we enable the SQL sandbox, so changes done to the database
- are reverted at the end of every test. If you are using
- PostgreSQL, you can even run database tests asynchronously
- by setting `use TempleDemoWeb.ConnCase, async: true`, although
- this option is not recommended for other databases.
- """
-
- use ExUnit.CaseTemplate
-
- using do
- quote do
- # Import conveniences for testing with connections
- import Plug.Conn
- import Phoenix.ConnTest
- import TempleDemoWeb.ConnCase
-
- alias TempleDemoWeb.Router.Helpers, as: Routes
-
- # The default endpoint for testing
- @endpoint TempleDemoWeb.Endpoint
- end
- end
-
- setup tags do
- :ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
-
- unless tags[:async] do
- Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
- end
-
- {:ok, conn: Phoenix.ConnTest.build_conn()}
- end
-end
diff --git a/integration_test/temple_demo/test/support/data_case.ex b/integration_test/temple_demo/test/support/data_case.ex
deleted file mode 100644
index 7dfbe0e..0000000
--- a/integration_test/temple_demo/test/support/data_case.ex
+++ /dev/null
@@ -1,55 +0,0 @@
-defmodule TempleDemo.DataCase do
- @moduledoc """
- This module defines the setup for tests requiring
- access to the application's data layer.
-
- You may define functions here to be used as helpers in
- your tests.
-
- Finally, if the test case interacts with the database,
- we enable the SQL sandbox, so changes done to the database
- are reverted at the end of every test. If you are using
- PostgreSQL, you can even run database tests asynchronously
- by setting `use TempleDemo.DataCase, async: true`, although
- this option is not recommended for other databases.
- """
-
- use ExUnit.CaseTemplate
-
- using do
- quote do
- alias TempleDemo.Repo
-
- import Ecto
- import Ecto.Changeset
- import Ecto.Query
- import TempleDemo.DataCase
- end
- end
-
- setup tags do
- :ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
-
- unless tags[:async] do
- Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
- end
-
- :ok
- end
-
- @doc """
- A helper that transforms changeset errors into a map of messages.
-
- assert {:error, changeset} = Accounts.create_user(%{password: "short"})
- assert "password is too short" in errors_on(changeset).password
- assert %{password: ["password is too short"]} = errors_on(changeset)
-
- """
- def errors_on(changeset) do
- Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
- Regex.replace(~r"%{(\w+)}", message, fn _, key ->
- opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
- end)
- end)
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo/blog_test.exs b/integration_test/temple_demo/test/temple_demo/blog_test.exs
deleted file mode 100644
index 8a6c7f3..0000000
--- a/integration_test/temple_demo/test/temple_demo/blog_test.exs
+++ /dev/null
@@ -1,80 +0,0 @@
-defmodule TempleDemo.BlogTest do
- use TempleDemo.DataCase
-
- alias TempleDemo.Blog
-
- describe "posts" do
- alias TempleDemo.Blog.Post
-
- @valid_attrs %{
- author: "some author",
- body: "some body",
- published_at: ~N[2010-04-17 14:00:00],
- title: "some title"
- }
- @update_attrs %{
- author: "some updated author",
- body: "some updated body",
- published_at: ~N[2011-05-18 15:01:01],
- title: "some updated title"
- }
- @invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
-
- def post_fixture(attrs \\ %{}) do
- {:ok, post} =
- attrs
- |> Enum.into(@valid_attrs)
- |> Blog.create_post()
-
- post
- end
-
- test "list_posts/0 returns all posts" do
- post = post_fixture()
- assert Blog.list_posts() == [post]
- end
-
- test "get_post!/1 returns the post with given id" do
- post = post_fixture()
- assert Blog.get_post!(post.id) == post
- end
-
- test "create_post/1 with valid data creates a post" do
- assert {:ok, %Post{} = post} = Blog.create_post(@valid_attrs)
- assert post.author == "some author"
- assert post.body == "some body"
- assert post.published_at == ~N[2010-04-17 14:00:00]
- assert post.title == "some title"
- end
-
- test "create_post/1 with invalid data returns error changeset" do
- assert {:error, %Ecto.Changeset{}} = Blog.create_post(@invalid_attrs)
- end
-
- test "update_post/2 with valid data updates the post" do
- post = post_fixture()
- assert {:ok, %Post{} = post} = Blog.update_post(post, @update_attrs)
- assert post.author == "some updated author"
- assert post.body == "some updated body"
- assert post.published_at == ~N[2011-05-18 15:01:01]
- assert post.title == "some updated title"
- end
-
- test "update_post/2 with invalid data returns error changeset" do
- post = post_fixture()
- assert {:error, %Ecto.Changeset{}} = Blog.update_post(post, @invalid_attrs)
- assert post == Blog.get_post!(post.id)
- end
-
- test "delete_post/1 deletes the post" do
- post = post_fixture()
- assert {:ok, %Post{}} = Blog.delete_post(post)
- assert_raise Ecto.NoResultsError, fn -> Blog.get_post!(post.id) end
- end
-
- test "change_post/1 returns a post changeset" do
- post = post_fixture()
- assert %Ecto.Changeset{} = Blog.change_post(post)
- end
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/controllers/page_controller_test.exs b/integration_test/temple_demo/test/temple_demo_web/controllers/page_controller_test.exs
deleted file mode 100644
index b6c60a9..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/controllers/page_controller_test.exs
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule TempleDemoWeb.PageControllerTest do
- use TempleDemoWeb.ConnCase
-
- test "GET /", %{conn: conn} do
- conn = get(conn, "/")
- assert html_response(conn, 200) =~ "Welcome to Phoenix!"
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/controllers/post_controller_test.exs b/integration_test/temple_demo/test/temple_demo_web/controllers/post_controller_test.exs
deleted file mode 100644
index f226242..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/controllers/post_controller_test.exs
+++ /dev/null
@@ -1,99 +0,0 @@
-defmodule TempleDemoWeb.PostControllerTest do
- use TempleDemoWeb.ConnCase
-
- alias TempleDemo.Blog
-
- @create_attrs %{
- author: "some author",
- body: "some body",
- published_at: ~N[2010-04-17 14:00:00],
- title: "some title"
- }
- @update_attrs %{
- author: "some updated author",
- body: "some updated body",
- published_at: ~N[2011-05-18 15:01:01],
- title: "some updated title"
- }
- @invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
-
- def fixture(:post) do
- {:ok, post} = Blog.create_post(@create_attrs)
- post
- end
-
- describe "index" do
- test "lists all posts", %{conn: conn} do
- conn = get(conn, Routes.post_path(conn, :index))
- assert html_response(conn, 200) =~ "Listing Posts"
- end
- end
-
- describe "new post" do
- test "renders form", %{conn: conn} do
- conn = get(conn, Routes.post_path(conn, :new))
- assert html_response(conn, 200) =~ "New Post"
- end
- end
-
- describe "create post" do
- test "redirects to show when data is valid", %{conn: conn} do
- conn = post(conn, Routes.post_path(conn, :create), post: @create_attrs)
-
- assert %{id: id} = redirected_params(conn)
- assert redirected_to(conn) == Routes.post_path(conn, :show, id)
-
- conn = get(conn, Routes.post_path(conn, :show, id))
- assert html_response(conn, 200) =~ "Show Post"
- end
-
- test "renders errors when data is invalid", %{conn: conn} do
- conn = post(conn, Routes.post_path(conn, :create), post: @invalid_attrs)
- assert html_response(conn, 200) =~ "New Post"
- end
- end
-
- describe "edit post" do
- setup [:create_post]
-
- test "renders form for editing chosen post", %{conn: conn, post: post} do
- conn = get(conn, Routes.post_path(conn, :edit, post))
- assert html_response(conn, 200) =~ "Edit Post"
- end
- end
-
- describe "update post" do
- setup [:create_post]
-
- test "redirects when data is valid", %{conn: conn, post: post} do
- conn = put(conn, Routes.post_path(conn, :update, post), post: @update_attrs)
- assert redirected_to(conn) == Routes.post_path(conn, :show, post)
-
- conn = get(conn, Routes.post_path(conn, :show, post))
- assert html_response(conn, 200) =~ "some updated author"
- end
-
- test "renders errors when data is invalid", %{conn: conn, post: post} do
- conn = put(conn, Routes.post_path(conn, :update, post), post: @invalid_attrs)
- assert html_response(conn, 200) =~ "Edit Post"
- end
- end
-
- describe "delete post" do
- setup [:create_post]
-
- test "deletes chosen post", %{conn: conn, post: post} do
- conn = delete(conn, Routes.post_path(conn, :delete, post))
- assert redirected_to(conn) == Routes.post_path(conn, :index)
-
- assert_error_sent 404, fn ->
- get(conn, Routes.post_path(conn, :show, post))
- end
- end
- end
-
- defp create_post(_) do
- post = fixture(:post)
- {:ok, post: post}
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs b/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs
deleted file mode 100644
index 2a42384..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule TempleDemoWeb.TempleFeatureTest do
- use ExUnit.Case, async: false
- use Wallaby.Feature
- alias TempleDemoWeb.Router.Helpers, as: Routes
- @endpoint TempleDemoWeb.Endpoint
-
- feature "renders the homepage", %{session: session} do
- session
- |> visit("/")
- |> assert_text("Welcome to Phoenix!")
- |> assert_text("inner content of outer")
- end
-
- feature "case statements work", %{session: session} do
- session
- |> visit("/?text=staging")
- |> assert_text("Welcome to Phoenix!")
- |> assert_text("Peace-of-mind from prototype to staging")
- |> visit("/?text=foobar")
- |> assert_text("Welcome to Phoenix!")
- |> assert_text("Peace-of-mind from prototype to production")
- end
-
- feature "can create a new post", %{session: session} do
- session
- |> visit(Routes.post_path(@endpoint, :index))
- |> click(Query.link("New Post"))
- |> 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")
- |> find(Query.select("post_published_at_year"), fn s ->
- s |> click(Query.option("2020"))
- end)
- |> find(Query.select("post_published_at_month"), fn s ->
- s |> click(Query.option("May"))
- end)
- |> find(Query.select("post_published_at_day"), fn s ->
- s |> click(Query.option("21"))
- end)
- |> fill_in(Query.text_field("Author"), with: "Mitchelob Ultra")
- |> assert_has(Query.css("#disabled-input[disabled]"))
- |> click(Query.button("Save"))
- |> assert_text("Post created successfully.")
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/views/error_view_test.exs b/integration_test/temple_demo/test/temple_demo_web/views/error_view_test.exs
deleted file mode 100644
index bda7ef1..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/views/error_view_test.exs
+++ /dev/null
@@ -1,14 +0,0 @@
-defmodule TempleDemoWeb.ErrorViewTest do
- use TempleDemoWeb.ConnCase, async: true
-
- # Bring render/3 and render_to_string/3 for testing custom views
- import Phoenix.View
-
- test "renders 404.html" do
- assert render_to_string(TempleDemoWeb.ErrorView, "404.html", []) == "Not Found"
- end
-
- test "renders 500.html" do
- assert render_to_string(TempleDemoWeb.ErrorView, "500.html", []) == "Internal Server Error"
- end
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/views/layout_view_test.exs b/integration_test/temple_demo/test/temple_demo_web/views/layout_view_test.exs
deleted file mode 100644
index d84d669..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/views/layout_view_test.exs
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule TempleDemoWeb.LayoutViewTest do
- use TempleDemoWeb.ConnCase, async: true
-
- # When testing helpers, you may want to import Phoenix.HTML and
- # use functions such as safe_to_string() to convert the helper
- # result into an HTML string.
- # import Phoenix.HTML
-end
diff --git a/integration_test/temple_demo/test/temple_demo_web/views/page_view_test.exs b/integration_test/temple_demo/test/temple_demo_web/views/page_view_test.exs
deleted file mode 100644
index d6e0210..0000000
--- a/integration_test/temple_demo/test/temple_demo_web/views/page_view_test.exs
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule TempleDemoWeb.PageViewTest do
- use TempleDemoWeb.ConnCase, async: true
-end
diff --git a/integration_test/temple_demo/test/test_helper.exs b/integration_test/temple_demo/test/test_helper.exs
deleted file mode 100644
index ab9695a..0000000
--- a/integration_test/temple_demo/test/test_helper.exs
+++ /dev/null
@@ -1,3 +0,0 @@
-Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, :manual)
-
-ExUnit.start()
diff --git a/integration_test/temple_plug_demo/.formatter.exs b/integration_test/temple_plug_demo/.formatter.exs
new file mode 100644
index 0000000..6ab28bf
--- /dev/null
+++ b/integration_test/temple_plug_demo/.formatter.exs
@@ -0,0 +1,5 @@
+# Used by "mix format"
+[
+ import_deps: [:plug, :temple],
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
diff --git a/integration_test/temple_demo/.gitignore b/integration_test/temple_plug_demo/.gitignore
similarity index 78%
rename from integration_test/temple_demo/.gitignore
rename to integration_test/temple_plug_demo/.gitignore
index 50c2c65..1317118 100644
--- a/integration_test/temple_demo/.gitignore
+++ b/integration_test/temple_plug_demo/.gitignore
@@ -7,7 +7,7 @@
# The directory Mix downloads your dependencies sources to.
/deps/
-# Where 3rd-party dependencies like ExDoc output generated docs.
+# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
@@ -20,7 +20,7 @@ erl_crash.dump
*.ez
# Ignore package tarball (built via "mix hex.build").
-temple_demo-*.tar
-
-/screenshots/
+temple_plug_demo-*.tar
+# Temporary files, for example, from tests.
+/tmp/
diff --git a/integration_test/temple_plug_demo/README.md b/integration_test/temple_plug_demo/README.md
new file mode 100644
index 0000000..4a4cb93
--- /dev/null
+++ b/integration_test/temple_plug_demo/README.md
@@ -0,0 +1,21 @@
+# TemplePlugDemo
+
+**TODO: Add description**
+
+## Installation
+
+If [available in Hex](https://hex.pm/docs/publish), the package can be installed
+by adding `temple_plug_demo` to your list of dependencies in `mix.exs`:
+
+```elixir
+def deps do
+ [
+ {:temple_plug_demo, "~> 0.1.0"}
+ ]
+end
+```
+
+Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
+and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
+be found at .
+
diff --git a/integration_test/temple_plug_demo/assets/css/app.css b/integration_test/temple_plug_demo/assets/css/app.css
new file mode 100644
index 0000000..1f8afc3
--- /dev/null
+++ b/integration_test/temple_plug_demo/assets/css/app.css
@@ -0,0 +1,5 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
+
+
diff --git a/integration_test/temple_plug_demo/assets/tailwind.config.js b/integration_test/temple_plug_demo/assets/tailwind.config.js
new file mode 100644
index 0000000..a2874cf
--- /dev/null
+++ b/integration_test/temple_plug_demo/assets/tailwind.config.js
@@ -0,0 +1,13 @@
+// See the Tailwind configuration guide for advanced usage
+// https://tailwindcss.com/docs/configuration
+module.exports = {
+ content: [
+ '../lib/**/*.*ex'
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [
+ require('@tailwindcss/forms')
+ ]
+}
diff --git a/integration_test/temple_plug_demo/config/config.exs b/integration_test/temple_plug_demo/config/config.exs
new file mode 100644
index 0000000..cabf62c
--- /dev/null
+++ b/integration_test/temple_plug_demo/config/config.exs
@@ -0,0 +1,15 @@
+import Config
+
+config :tailwind,
+ version: "3.0.24",
+ default: [
+ args: ~w(
+ --config=tailwind.config.js
+ --input=css/app.css
+ --output=../priv/static/assets/app.css
+ ),
+ cd: Path.expand("../assets", __DIR__)
+ ]
+
+config :temple_plug_demo, :watchers,
+ tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
diff --git a/integration_test/temple_plug_demo/lib/temple_plug_demo.ex b/integration_test/temple_plug_demo/lib/temple_plug_demo.ex
new file mode 100644
index 0000000..21a76a6
--- /dev/null
+++ b/integration_test/temple_plug_demo/lib/temple_plug_demo.ex
@@ -0,0 +1,18 @@
+defmodule TemplePlugDemo do
+ @moduledoc """
+ Documentation for `TemplePlugDemo`.
+ """
+
+ @doc """
+ Hello world.
+
+ ## Examples
+
+ iex> TemplePlugDemo.hello()
+ :world
+
+ """
+ def hello do
+ :world
+ end
+end
diff --git a/integration_test/temple_plug_demo/lib/temple_plug_demo/application.ex b/integration_test/temple_plug_demo/lib/temple_plug_demo/application.ex
new file mode 100644
index 0000000..cd0e3cb
--- /dev/null
+++ b/integration_test/temple_plug_demo/lib/temple_plug_demo/application.ex
@@ -0,0 +1,25 @@
+defmodule TemplePlugDemo.Application do
+ # See https://hexdocs.pm/elixir/Application.html
+ # for more information on OTP Applications
+ @moduledoc false
+
+ use Application
+
+ @impl true
+
+ def start(_type, _args) do
+ children =
+ [
+ {Bandit, plug: TemplePlugDemo.Router, scheme: :http, options: [port: 4001]}
+ ] ++ watcher_children()
+
+ opts = [strategy: :one_for_one, name: MyApp.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+
+ def watcher_children() do
+ for args <- Application.get_env(:temple_plug_demo, :watchers, []) do
+ {TemplePlugDemo.Watcher, args}
+ end
+ end
+end
diff --git a/integration_test/temple_plug_demo/lib/temple_plug_demo/router.ex b/integration_test/temple_plug_demo/lib/temple_plug_demo/router.ex
new file mode 100644
index 0000000..5e68cf7
--- /dev/null
+++ b/integration_test/temple_plug_demo/lib/temple_plug_demo/router.ex
@@ -0,0 +1,47 @@
+defmodule TemplePlugDemo.Router do
+ use Plug.Router
+
+ import Temple
+
+ plug Plug.Static, from: {:temple_plug_demo, "priv/static"}, at: "/static"
+
+ plug :match
+ plug :dispatch
+
+ get "/" do
+ assigns = %{title: "Motch App"}
+
+ response =
+ temple do
+ ""
+
+ html do
+ head do
+ title do: @title
+
+ link rel: "stylesheet", href: "/static/assets/app.css"
+ end
+
+ body class: "font-sans container mx-auto" do
+ span do
+ "loose"
+ end
+
+ span do: "tight"
+
+ div do
+ "world"
+ end
+ end
+ end
+ end
+
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, response)
+ end
+
+ match _ do
+ send_resp(conn, 404, "oops")
+ end
+end
diff --git a/integration_test/temple_plug_demo/lib/temple_plug_demo/watcher.ex b/integration_test/temple_plug_demo/lib/temple_plug_demo/watcher.ex
new file mode 100644
index 0000000..1f35bcf
--- /dev/null
+++ b/integration_test/temple_plug_demo/lib/temple_plug_demo/watcher.ex
@@ -0,0 +1,62 @@
+# module mostly taken from `Phoenix.Endpoint.Watcher`
+
+defmodule TemplePlugDemo.Watcher do
+ @moduledoc false
+ require Logger
+
+ def child_spec(args) do
+ %{
+ id: make_ref(),
+ start: {__MODULE__, :start_link, [args]},
+ restart: :transient
+ }
+ end
+
+ def start_link({cmd, args}) do
+ Task.start_link(__MODULE__, :watch, [to_string(cmd), args])
+ end
+
+ def watch(_cmd, {mod, fun, args}) do
+ try do
+ apply(mod, fun, args)
+ catch
+ kind, reason ->
+ # The function returned a non-zero exit code.
+ # Sleep for a couple seconds before exiting to
+ # ensure this doesn't hit the supervisor's
+ # max_restarts/max_seconds limit.
+ Process.sleep(2000)
+ :erlang.raise(kind, reason, __STACKTRACE__)
+ end
+ end
+
+ def watch(cmd, args) when is_list(args) do
+ {args, opts} = Enum.split_while(args, &is_binary(&1))
+ opts = Keyword.merge([into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts)
+
+ try do
+ System.cmd(cmd, args, opts)
+ catch
+ :error, :enoent ->
+ relative = Path.relative_to_cwd(cmd)
+
+ Logger.error(
+ "Could not start watcher #{inspect(relative)} from #{inspect(cd(opts))}, executable does not exist"
+ )
+
+ exit(:shutdown)
+ else
+ {_, 0} ->
+ :ok
+
+ {_, _} ->
+ # System.cmd returned a non-zero exit code
+ # sleep for a couple seconds before exiting to ensure this doesn't
+ # hit the supervisor's max_restarts / max_seconds limit
+ Process.sleep(2000)
+ exit(:watcher_command_error)
+ end
+ end
+
+ defp cd(opts), do: opts[:cd] || File.cwd!()
+end
diff --git a/integration_test/temple_plug_demo/mix.exs b/integration_test/temple_plug_demo/mix.exs
new file mode 100644
index 0000000..be84fb2
--- /dev/null
+++ b/integration_test/temple_plug_demo/mix.exs
@@ -0,0 +1,33 @@
+defmodule TemplePlugDemo.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :temple_plug_demo,
+ version: "0.1.0",
+ elixir: "~> 1.13",
+ compilers: [:temple] ++ Mix.compilers(),
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger],
+ mod: {TemplePlugDemo.Application, []}
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ [
+ {:bandit, "~> 0.4.9"},
+ {:tailwind, "~> 0.1", runtime: Mix.env() == :dev},
+ {:temple, path: "../../"}
+ # {:dep_from_hexpm, "~> 0.3.0"},
+ # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
+ ]
+ end
+end
diff --git a/integration_test/temple_plug_demo/mix.lock b/integration_test/temple_plug_demo/mix.lock
new file mode 100644
index 0000000..ffd8a31
--- /dev/null
+++ b/integration_test/temple_plug_demo/mix.lock
@@ -0,0 +1,11 @@
+%{
+ "bandit": {:hex, :bandit, "0.4.9", "8045b78a5087a51144a1a20bcbba879c989e659946e4ce27822726235b1d0d43", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.12", [hex: :plug, repo: "hexpm", optional: false]}, {:thousand_island, "~> 0.5.7", [hex: :thousand_island, repo: "hexpm", optional: false]}], "hexpm", "b04cba7e5530cc094d971c3664b0084c06e1f6d74518f4ddc92d0d00ebe6ca59"},
+ "castore": {:hex, :castore, "0.1.16", "2675f717adc700475345c5512c381ef9273eb5df26bdd3f8c13e2636cf4cc175", [:mix], [], "hexpm", "28ed2c43d83b5c25d35c51bc0abf229ac51359c170cba76171a462ced2e4b651"},
+ "hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"},
+ "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
+ "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
+ "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
+ "tailwind": {:hex, :tailwind, "0.1.5", "5561bed6c114434415077972f6d291e7d43b258ef0ee756bda1ead7293811f61", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "3be21a0ddec7fc29b323ee72bed7516078a2787f7b142e455698a2209296e2a5"},
+ "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
+ "thousand_island": {:hex, :thousand_island, "0.5.7", "5066fb800287a9eb2699365746f51853ddc5ed0d4d39ea4c8b906f40b14a2499", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5d79e910a9d295e75ee4022df3a68173588dcc9a7e4dfcce4876325c8a9f51a7"},
+}
diff --git a/integration_test/temple_plug_demo/priv/static/assets/app.css b/integration_test/temple_plug_demo/priv/static/assets/app.css
new file mode 100644
index 0000000..71d3fba
--- /dev/null
+++ b/integration_test/temple_plug_demo/priv/static/assets/app.css
@@ -0,0 +1,684 @@
+/*
+! tailwindcss v3.0.24 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+*/
+
+html {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-size: 1em;
+ /* 2 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input:-ms-input-placeholder, textarea:-ms-input-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/*
+Ensure the default browser behavior of the `hidden` attribute.
+*/
+
+[hidden] {
+ display: none;
+}
+
+[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background-color: #fff;
+ border-color: #6b7280;
+ border-width: 1px;
+ border-radius: 0px;
+ padding-top: 0.5rem;
+ padding-right: 0.75rem;
+ padding-bottom: 0.5rem;
+ padding-left: 0.75rem;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ --tw-shadow: 0 0 #0000;
+}
+
+[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #2563eb;
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ border-color: #2563eb;
+}
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ color: #6b7280;
+ opacity: 1;
+}
+
+input:-ms-input-placeholder, textarea:-ms-input-placeholder {
+ color: #6b7280;
+ opacity: 1;
+}
+
+input::placeholder,textarea::placeholder {
+ color: #6b7280;
+ opacity: 1;
+}
+
+::-webkit-datetime-edit-fields-wrapper {
+ padding: 0;
+}
+
+::-webkit-date-and-time-value {
+ min-height: 1.5em;
+}
+
+select {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
+ background-position: right 0.5rem center;
+ background-repeat: no-repeat;
+ background-size: 1.5em 1.5em;
+ padding-right: 2.5rem;
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+}
+
+[multiple] {
+ background-image: initial;
+ background-position: initial;
+ background-repeat: unset;
+ background-size: initial;
+ padding-right: 0.75rem;
+ -webkit-print-color-adjust: unset;
+ color-adjust: unset;
+}
+
+[type='checkbox'],[type='radio'] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ padding: 0;
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+ display: inline-block;
+ vertical-align: middle;
+ background-origin: border-box;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ flex-shrink: 0;
+ height: 1rem;
+ width: 1rem;
+ color: #2563eb;
+ background-color: #fff;
+ border-color: #6b7280;
+ border-width: 1px;
+ --tw-shadow: 0 0 #0000;
+}
+
+[type='checkbox'] {
+ border-radius: 0px;
+}
+
+[type='radio'] {
+ border-radius: 100%;
+}
+
+[type='checkbox']:focus,[type='radio']:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
+ --tw-ring-offset-width: 2px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #2563eb;
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+}
+
+[type='checkbox']:checked,[type='radio']:checked {
+ border-color: transparent;
+ background-color: currentColor;
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+[type='checkbox']:checked {
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+}
+
+[type='radio']:checked {
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+}
+
+[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
+ border-color: transparent;
+ background-color: currentColor;
+}
+
+[type='checkbox']:indeterminate {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
+ border-color: transparent;
+ background-color: currentColor;
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
+ border-color: transparent;
+ background-color: currentColor;
+}
+
+[type='file'] {
+ background: unset;
+ border-color: inherit;
+ border-width: 0;
+ border-radius: 0;
+ padding: 0;
+ font-size: unset;
+ line-height: inherit;
+}
+
+[type='file']:focus {
+ outline: 1px auto -webkit-focus-ring-color;
+}
+
+*, ::before, ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
+.relative {
+ position: relative;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.font-sans {
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+
diff --git a/integration_test/temple_plug_demo/test/temple_plug_demo_test.exs b/integration_test/temple_plug_demo/test/temple_plug_demo_test.exs
new file mode 100644
index 0000000..49d8073
--- /dev/null
+++ b/integration_test/temple_plug_demo/test/temple_plug_demo_test.exs
@@ -0,0 +1,8 @@
+defmodule TemplePlugDemoTest do
+ use ExUnit.Case
+ doctest TemplePlugDemo
+
+ test "greets the world" do
+ assert TemplePlugDemo.hello() == :world
+ end
+end
diff --git a/integration_test/temple_plug_demo/test/test_helper.exs b/integration_test/temple_plug_demo/test/test_helper.exs
new file mode 100644
index 0000000..869559e
--- /dev/null
+++ b/integration_test/temple_plug_demo/test/test_helper.exs
@@ -0,0 +1 @@
+ExUnit.start()
diff --git a/lib/mix/tasks/compile.temple.ex b/lib/mix/tasks/compile.temple.ex
new file mode 100644
index 0000000..fe83a85
--- /dev/null
+++ b/lib/mix/tasks/compile.temple.ex
@@ -0,0 +1,15 @@
+defmodule Mix.Tasks.Compile.Temple do
+ use Mix.Task.Compiler
+
+ @recursive true
+
+ @impl Mix.Task.Compiler
+ def run(_) do
+ Code.put_compiler_option(
+ :parser_options,
+ Keyword.put(Code.get_compiler_option(:parser_options), :token_metadata, true)
+ )
+
+ {:ok, []}
+ end
+end
diff --git a/lib/mix/tasks/temple.gen.html.ex b/lib/mix/tasks/temple.gen.html.ex
deleted file mode 100644
index 330de46..0000000
--- a/lib/mix/tasks/temple.gen.html.ex
+++ /dev/null
@@ -1,263 +0,0 @@
-if Code.ensure_loaded?(Mix.Phoenix) do
- defmodule Mix.Tasks.Temple.Gen.Html do
- @shortdoc "Generates controller, views, and context for an HTML resource in Temple"
-
- @moduledoc """
- Generates controller, views, and context for an HTML resource in Temple.
-
- mix temple.gen.html Accounts User users name:string age:integer
-
- The first argument is the context module followed by the schema module
- and its plural name (used as the schema table name).
-
- The context is an Elixir module that serves as an API boundary for
- the given resource. A context often holds many related resources.
- Therefore, if the context already exists, it will be augmented with
- functions for the given resource.
-
- > Note: A resource may also be split
- > over distinct contexts (such as `Accounts.User` and `Payments.User`).
-
- The schema is responsible for mapping the database fields into an
- Elixir struct.
-
- Overall, this generator will add the following files to `lib/`:
-
- * a context module in `lib/app/accounts.ex` for the accounts API
- * a schema in `lib/app/accounts/user.ex`, with an `users` table
- * a view in `lib/app_web/views/user_view.ex`
- * a controller in `lib/app_web/controllers/user_controller.ex`
- * default CRUD templates in `lib/app_web/templates/user`
-
- A migration file for the repository and test files for the context and
- controller features will also be generated.
-
- The location of the web files (controllers, views, templates, etc) in an
- umbrella application will vary based on the `:context_app` config located
- in your applications `:generators` configuration. When set, the Phoenix
- generators will generate web files directly in your lib and test folders
- since the application is assumed to be isolated to web specific functionality.
- If `:context_app` is not set, the generators will place web related lib
- and test files in a `web/` directory since the application is assumed
- to be handling both web and domain specific functionality.
- Example configuration:
-
- config :my_app_web, :generators, context_app: :my_app
-
- Alternatively, the `--context-app` option may be supplied to the generator:
-
- mix phx.gen.html Sales User users --context-app warehouse
-
- ## Web namespace
-
- By default, the controller and view will be namespaced by the schema name.
- You can customize the web module namespace by passing the `--web` flag with a
- module name, for example:
-
- mix phx.gen.html.temple Sales User users --web Sales
-
- Which would generate a `lib/app_web/controllers/sales/user_controller.ex` and
- `lib/app_web/views/sales/user_view.ex`.
-
- ## Generating without a schema or context file
-
- In some cases, you may wish to bootstrap HTML templates, controllers, and
- controller tests, but leave internal implementation of the context or schema
- to yourself. You can use the `--no-context` and `--no-schema` flags for
- file generation control.
-
- ## table
-
- By default, the table name for the migration and schema will be
- the plural name provided for the resource. To customize this value,
- a `--table` option may be provided. For example:
-
- mix phx.gen.html.temple Accounts User users --table cms_users
-
- ## binary_id
-
- Generated migration can use `binary_id` for schema's primary key
- and its references with option `--binary-id`.
-
- ## Default options
-
- This generator uses default options provided in the `:generators`
- configuration of your application. These are the defaults:
-
- config :your_app, :generators,
- migration: true,
- binary_id: false,
- sample_binary_id: "11111111-1111-1111-1111-111111111111"
-
- You can override those options per invocation by providing corresponding
- switches, e.g. `--no-binary-id` to use normal ids despite the default
- configuration or `--migration` to force generation of the migration.
-
- Read the documentation for `phx.gen.schema` for more information on
- attributes.
- """
- use Mix.Task
-
- alias Mix.Phoenix.{Context, Schema}
- alias Mix.Tasks.Phx.Gen
-
- @doc false
- def run(args) do
- if Mix.Project.umbrella?() do
- Mix.raise("mix temple.gen.html can only be run inside an application directory")
- end
-
- {context, schema} = Gen.Context.build(args)
- Gen.Context.prompt_for_code_injection(context)
-
- binding = [context: context, schema: schema, inputs: inputs(schema)]
- paths = [".", :temple]
-
- prompt_for_conflicts(context)
-
- context
- |> copy_new_files(paths, binding)
- |> print_shell_instructions()
- end
-
- defp prompt_for_conflicts(context) do
- context
- |> files_to_be_generated()
- |> Kernel.++(context_files(context))
- |> Mix.Phoenix.prompt_for_conflicts()
- end
-
- defp context_files(%Context{generate?: true} = context) do
- Gen.Context.files_to_be_generated(context)
- end
-
- defp context_files(%Context{generate?: false}) do
- []
- end
-
- @doc false
- def files_to_be_generated(%Context{schema: schema, context_app: context_app}) do
- web_prefix = Mix.Phoenix.web_path(context_app)
- test_prefix = Mix.Phoenix.web_test_path(context_app)
- web_path = to_string(schema.web_path)
-
- [
- {:eex, "controller.ex",
- Path.join([web_prefix, "controllers", web_path, "#{schema.singular}_controller.ex"])},
- {:eex, "edit.html.exs",
- Path.join([web_prefix, "templates", web_path, schema.singular, "edit.html.exs"])},
- {:eex, "form.html.exs",
- Path.join([web_prefix, "templates", web_path, schema.singular, "form.html.exs"])},
- {:eex, "index.html.exs",
- Path.join([web_prefix, "templates", web_path, schema.singular, "index.html.exs"])},
- {:eex, "new.html.exs",
- Path.join([web_prefix, "templates", web_path, schema.singular, "new.html.exs"])},
- {:eex, "show.html.exs",
- Path.join([web_prefix, "templates", web_path, schema.singular, "show.html.exs"])},
- {:eex, "view.ex",
- Path.join([web_prefix, "views", web_path, "#{schema.singular}_view.ex"])},
- {:eex, "controller_test.exs",
- Path.join([
- test_prefix,
- "controllers",
- web_path,
- "#{schema.singular}_controller_test.exs"
- ])}
- ]
- end
-
- @doc false
- def copy_new_files(%Context{} = context, paths, binding) do
- files = files_to_be_generated(context)
-
- Mix.Phoenix.copy_from(paths, "priv/templates/temple.gen.html", binding, files)
-
- if context.generate?,
- do: Gen.Context.copy_new_files(context, Mix.Phoenix.generator_paths(), binding)
-
- context
- end
-
- @doc false
- def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do
- if schema.web_namespace do
- Mix.shell().info("""
-
- Add the resource to your #{schema.web_namespace} :browser scope in #{
- Mix.Phoenix.web_path(ctx_app)
- }/router.ex:
-
- scope "/#{schema.web_path}", #{
- inspect(Module.concat(context.web_module, schema.web_namespace))
- }, as: :#{schema.web_path} do
- pipe_through :browser
- ...
- resources "/#{schema.plural}", #{inspect(schema.alias)}Controller
- end
- """)
- else
- Mix.shell().info("""
-
- Add the resource to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:
-
- resources "/#{schema.plural}", #{inspect(schema.alias)}Controller
- """)
- end
-
- if context.generate?, do: Gen.Context.print_shell_instructions(context)
- end
-
- def inputs(%Schema{} = schema) do
- Enum.map(schema.attrs, fn
- {_, {:references, _}} ->
- {nil, nil, nil}
-
- {key, :integer} ->
- {label(key), ~s(number_input f, #{inspect(key)}), error(key)}
-
- {key, :float} ->
- {label(key), ~s(number_input f, #{inspect(key)}, step: "any"), error(key)}
-
- {key, :decimal} ->
- {label(key), ~s(number_input f, #{inspect(key)}, step: "any"), error(key)}
-
- {key, :boolean} ->
- {label(key), ~s(checkbox f, #{inspect(key)}), error(key)}
-
- {key, :text} ->
- {label(key), ~s(textarea f, #{inspect(key)}), error(key)}
-
- {key, :date} ->
- {label(key), ~s(date_select f, #{inspect(key)}), error(key)}
-
- {key, :time} ->
- {label(key), ~s(time_select f, #{inspect(key)}), error(key)}
-
- {key, :utc_datetime} ->
- {label(key), ~s(datetime_select f, #{inspect(key)}), error(key)}
-
- {key, :naive_datetime} ->
- {label(key), ~s(datetime_select f, #{inspect(key)}), error(key)}
-
- {key, {:array, :integer}} ->
- {label(key), ~s(multiple_select f, #{inspect(key)}, ["1": 1, "2": 2]), error(key)}
-
- {key, {:array, _}} ->
- {label(key),
- ~s(multiple_select f, #{inspect(key)}, ["Option 1": "option1", "Option 2": "option2"]),
- error(key)}
-
- {key, _} ->
- {label(key), ~s(text_input f, #{inspect(key)}), error(key)}
- end)
- end
-
- defp label(key) do
- ~s(label f, #{inspect(key)})
- end
-
- defp error(field) do
- ~s{error_tag(f, #{inspect(field)})}
- end
- end
-end
diff --git a/lib/mix/tasks/temple.gen.layout.ex b/lib/mix/tasks/temple.gen.layout.ex
deleted file mode 100644
index 4c4a0a6..0000000
--- a/lib/mix/tasks/temple.gen.layout.ex
+++ /dev/null
@@ -1,31 +0,0 @@
-if Code.ensure_loaded?(Mix.Phoenix) do
- defmodule Mix.Tasks.Temple.Gen.Layout do
- use Mix.Task
-
- @shortdoc "Generates a default Phoenix layout file in Temple"
-
- @moduledoc """
- Generates a Phoenix layout file in Temple.
- mix temple.gen.layout
- """
- def run(_args) do
- context_app = Mix.Phoenix.context_app()
- web_prefix = Mix.Phoenix.web_path(context_app)
- binding = [application_module: Mix.Phoenix.base()]
-
- Mix.Phoenix.copy_from(temple_paths(), "priv/templates/temple.gen.layout", binding, [
- {:eex, "app.html.eex", "#{web_prefix}/templates/layout/app.html.exs"}
- ])
-
- instructions = """
- A new #{web_prefix}/templates/layout/app.html.exs file was generated.
- """
-
- Mix.shell().info(instructions)
- end
-
- defp temple_paths do
- [".", :temple]
- end
- end
-end
diff --git a/lib/mix/tasks/temple.gen.live.ex b/lib/mix/tasks/temple.gen.live.ex
deleted file mode 100644
index c68cb46..0000000
--- a/lib/mix/tasks/temple.gen.live.ex
+++ /dev/null
@@ -1,254 +0,0 @@
-if Code.ensure_loaded?(Mix.Phoenix) do
- defmodule Mix.Tasks.Temple.Gen.Live do
- @shortdoc "Generates LiveView, templates, and context for a resource"
-
- @moduledoc """
- Generates LiveView, templates, and context for a resource.
-
- mix temple.gen.live Accounts User users name:string age:integer
-
- The first argument is the context module followed by the schema module
- and its plural name (used as the schema table name).
-
- The context is an Elixir module that serves as an API boundary for
- the given resource. A context often holds many related resources.
- Therefore, if the context already exists, it will be augmented with
- functions for the given resource.
-
- When this command is run for the first time, a `ModalComponent` and
- `LiveHelpers` module will be created, along with the resource level
- LiveViews and components, including an `IndexLive`, `ShowLive`, `FormComponent`
- for the new resource.
-
- > Note: A resource may also be split
- > over distinct contexts (such as `Accounts.User` and `Payments.User`).
-
- The schema is responsible for mapping the database fields into an
- Elixir struct. It is followed by an optional list of attributes,
- with their respective names and types. See `mix phx.gen.schema`
- for more information on attributes.
-
- Overall, this generator will add the following files to `lib/`:
-
- * a context module in `lib/app/accounts.ex` for the accounts API
- * a schema in `lib/app/accounts/user.ex`, with an `users` table
- * a view in `lib/app_web/views/user_view.ex`
- * a LiveView in `lib/app_web/live/user_live/show_live.ex`
- * a LiveView in `lib/app_web/live/user_live/index_live.ex`
- * a LiveComponent in `lib/app_web/live/user_live/form_component.ex`
- * a LiveComponent in `lib/app_web/live/modal_component.ex`
- * a helpers modules in `lib/app_web/live/live_helpers.ex`
-
- ## The context app
-
- A migration file for the repository and test files for the context and
- controller features will also be generated.
-
- The location of the web files (LiveView's, views, templates, etc) in an
- umbrella application will vary based on the `:context_app` config located
- in your applications `:generators` configuration. When set, the Phoenix
- generators will generate web files directly in your lib and test folders
- since the application is assumed to be isolated to web specific functionality.
- If `:context_app` is not set, the generators will place web related lib
- and test files in a `web/` directory since the application is assumed
- to be handling both web and domain specific functionality.
- Example configuration:
-
- config :my_app_web, :generators, context_app: :my_app
-
- Alternatively, the `--context-app` option may be supplied to the generator:
-
- mix temple.gen.live Sales User users --context-app warehouse
-
- ## Web namespace
-
- By default, the controller and view will be namespaced by the schema name.
- You can customize the web module namespace by passing the `--web` flag with a
- module name, for example:
-
- mix temple.gen.live Sales User users --web Sales
-
- Which would generate a LiveViews inside `lib/app_web/live/sales/user_live/` and a
- view at `lib/app_web/views/sales/user_view.ex`.
-
- ## Customising the context, schema, tables and migrations
-
- In some cases, you may wish to bootstrap HTML templates, LiveViews,
- and tests, but leave internal implementation of the context or schema
- to yourself. You can use the `--no-context` and `--no-schema` flags
- for file generation control.
-
- You can also change the table name or configure the migrations to
- use binary ids for primary keys, see `mix phx.gen.schema` for more
- information.
- """
- use Mix.Task
-
- alias Mix.Phoenix.{Context}
- alias Mix.Tasks.Phx.Gen
-
- @doc false
- def run(args) do
- if Mix.Project.umbrella?() do
- Mix.raise("mix temple.gen.live can only be run inside an application directory")
- end
-
- {context, schema} = Gen.Context.build(args)
- Gen.Context.prompt_for_code_injection(context)
-
- binding = [
- context: context,
- schema: schema,
- inputs: Mix.Tasks.Temple.Gen.Html.inputs(schema)
- ]
-
- paths = [".", :temple]
-
- prompt_for_conflicts(context)
-
- context
- |> copy_new_files(binding, paths)
- |> maybe_inject_helpers()
- |> print_shell_instructions()
- end
-
- defp prompt_for_conflicts(context) do
- context
- |> files_to_be_generated()
- |> Kernel.++(context_files(context))
- |> Mix.Phoenix.prompt_for_conflicts()
- end
-
- defp context_files(%Context{generate?: true} = context) do
- Gen.Context.files_to_be_generated(context)
- end
-
- defp context_files(%Context{generate?: false}) do
- []
- end
-
- defp files_to_be_generated(%Context{schema: schema, context_app: context_app}) do
- web_prefix = Mix.Phoenix.web_path(context_app)
- test_prefix = Mix.Phoenix.web_test_path(context_app)
- web_path = to_string(schema.web_path)
- live_subdir = "#{schema.singular}_live"
-
- [
- {:eex, "show.ex", Path.join([web_prefix, "live", web_path, live_subdir, "show.ex"])},
- {:eex, "index.ex", Path.join([web_prefix, "live", web_path, live_subdir, "index.ex"])},
- {:eex, "form_component.ex",
- Path.join([web_prefix, "live", web_path, live_subdir, "form_component.ex"])},
- {:eex, "form_component.html.lexs",
- Path.join([web_prefix, "live", web_path, live_subdir, "form_component.html.lexs"])},
- {:eex, "index.html.lexs",
- Path.join([web_prefix, "live", web_path, live_subdir, "index.html.lexs"])},
- {:eex, "show.html.lexs",
- Path.join([web_prefix, "live", web_path, live_subdir, "show.html.lexs"])},
- {:eex, "live_test.exs",
- Path.join([test_prefix, "live", web_path, "#{schema.singular}_live_test.exs"])},
- {:new_eex, "modal_component.ex", Path.join([web_prefix, "live", "modal_component.ex"])},
- {:new_eex, "live_helpers.ex", Path.join([web_prefix, "live", "live_helpers.ex"])}
- ]
- end
-
- defp copy_new_files(%Context{} = context, binding, paths) do
- files = files_to_be_generated(context)
-
- Mix.Phoenix.copy_from(
- paths,
- "priv/templates/temple.gen.live",
- binding,
- files
- )
-
- if context.generate?,
- do: Gen.Context.copy_new_files(context, Mix.Phoenix.generator_paths(), binding)
-
- context
- end
-
- defp maybe_inject_helpers(%Context{context_app: ctx_app} = context) do
- web_prefix = Mix.Phoenix.web_path(ctx_app)
- [lib_prefix, web_dir] = Path.split(web_prefix)
- file_path = Path.join(lib_prefix, "#{web_dir}.ex")
- file = File.read!(file_path)
- inject = "import #{inspect(context.web_module)}.LiveHelpers"
-
- if String.contains?(file, inject) do
- :ok
- else
- do_inject_helpers(context, file, file_path, inject)
- end
-
- context
- end
-
- defp do_inject_helpers(context, file, file_path, inject) do
- Mix.shell().info([:green, "* injecting ", :reset, Path.relative_to_cwd(file_path)])
-
- new_file =
- String.replace(
- file,
- "import Phoenix.LiveView.Helpers",
- "import Phoenix.LiveView.Helpers\n #{inject}"
- )
-
- if file != new_file do
- File.write!(file_path, new_file)
- else
- Mix.shell().info("""
-
- Could not find Phoenix.LiveView.Helpers imported in #{file_path}.
-
- This typically happens because your application was not generated
- with the --live flag:
-
- mix temple.new my_app --live
-
- Please make sure LiveView is installed and that #{inspect(context.web_module)}
- defines both `live_view/0` and `live_component/0` functions,
- and that both functions import #{inspect(context.web_module)}.LiveHelpers.
- """)
- end
- end
-
- @doc false
- def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do
- prefix = Module.concat(context.web_module, schema.web_namespace)
- web_path = Mix.Phoenix.web_path(ctx_app)
-
- if schema.web_namespace do
- Mix.shell().info("""
-
- Add the live routes to your #{schema.web_namespace} :browser scope in #{web_path}/router.ex:
-
- scope "/#{schema.web_path}", #{inspect(prefix)}, as: :#{schema.web_path} do
- pipe_through :browser
- ...
-
- #{for line <- live_route_instructions(schema), do: " #{line}"}
- end
- """)
- else
- Mix.shell().info("""
-
- Add the live routes to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:
-
- #{for line <- live_route_instructions(schema), do: " #{line}"}
- """)
- end
-
- if context.generate?, do: Gen.Context.print_shell_instructions(context)
- end
-
- defp live_route_instructions(schema) do
- [
- ~s|live "/#{schema.plural}", #{inspect(schema.alias)}Live.Index, :index\n|,
- ~s|live "/#{schema.plural}/new", #{inspect(schema.alias)}Live.Index, :new\n|,
- ~s|live "/#{schema.plural}/:id/edit", #{inspect(schema.alias)}Live.Index, :edit\n\n|,
- ~s|live "/#{schema.plural}/:id", #{inspect(schema.alias)}Live.Show, :show\n|,
- ~s|live "/#{schema.plural}/:id/show/edit", #{inspect(schema.alias)}Live.Show, :edit|
- ]
- end
- end
-end
diff --git a/lib/temple.ex b/lib/temple.ex
index d6b3399..0b8cd1f 100644
--- a/lib/temple.ex
+++ b/lib/temple.ex
@@ -1,120 +1,67 @@
defmodule Temple do
- alias Temple.Parser
+ @engine Application.compile_env(:temple, :engine, EEx.SmartEngine)
@moduledoc """
- Temple syntax is available inside the `temple`, and is compiled into EEx at build time.
+ Temple syntax is available inside the `temple`, and is compiled into efficient Elixir code at compile time using the configured `EEx.Engine`.
+
+ You should checkout the [guides](https://hexdocs.pm/temple/your-first-template.html) for a more in depth explanation.
## Usage
```elixir
- temple do
- # You can define attributes by passing a keyword list to the element, the values can be literals or variables.
- class = "text-blue"
- id = "jumbotron"
+ defmodule MyApp.HomePage do
+ import Temple
- div class: class, id: id do
- # Text nodes can be emitted as string literals or variables.
- "Bob"
+ def render() do
+ assigns = %{title: "My Site | Sign Up", logged_in: false}
- id
- end
+ temple do
+ ""
- # Attributes that result in boolean values will be emitted as a boolean attribute. Examples of boolean attributes are `disabled` and `checked`.
+ html do
+ head do
+ meta charset: "utf-8"
+ meta http_equiv: "X-UA-Compatible", content: "IE=edge"
+ meta name: "viewport", content: "width=device-width, initial-scale=1.0"
+ link rel: "stylesheet", href: "/css/app.css"
- input type: "text", disabled: true
- #
+ title do: @title
+ end
- input type: "text", disabled: false
- #
+ body do
+ header class: "header" do
+ ul do
+ li do
+ a href: "/", do: "Home"
+ end
+ li do
+ if @logged_in do
+ a href: "/logout", do: "Logout"
+ else
+ a href: "/login", do: "Login"
+ end
+ end
+ end
+ end
- # The class attribute also can take a keyword list of classes to conditionally render, based on the boolean result of the value.
-
- div class: ["text-red-500": false, "text-green-500": true] do
- "Alert!"
- end
-
- #
Alert!
-
- # if and unless expressions can be used to conditionally render content
- if 5 > 0 do
- p do
- "Greater than 0!"
+ main do
+ "Hi! Welcome to my website."
+ end
+ end
+ end
end
end
-
- unless 5 > 0 do
- p do
- "Less than 0!"
- end
- end
-
- # You can loop over items using for comprehensions
- for x <- 0..5 do
- div do
- x
- end
- end
-
- # You can use multiline anonymous functions, like if you're building a form in Phoenix
- form_for @changeset, Routes.user_path(@conn, :create), fn f ->
- "Name: "
- text_input f, :name
- end
-
- # You can explicitly emit a tag by prefixing with the Temple module
- Temple.div do
- "Foo"
- end
-
- # You can also pass children as a do key instead of a block
- div do: "Alice", class: "text-yellow"
end
```
- ## Whitespace Control
-
- By default, Temple will emit internal whitespace into tags, something like this.
-
- ```elixir
- span do
- "Hello, world!"
- end
- ```
-
- ```html
-
- Hello, world!
-
- ```
-
- If you need to create a "tight" tag, you can call the "bang" version of the desired tag.
-
- ```elixir
- span! do
- "Hello, world!"
- end
- ```
-
- ```html
- Hello, world!
- ```
-
## Configuration
- ### Mode
+ ### Engine
- There are two "modes", `:normal` (the default) and `:live_view`.
-
- In `:live_view` mode, Temple emits markup that uses functions provided by Phoenix LiveView in order to be fully "diff trackable". These LiveView functions have not been released yet, so if you are going to combine Temple with LiveView, you need to use the latest unreleased default branch from GitHub.
-
- You should use `:live_view` mode even if you only have a single LiveView.
+ By default Temple wil use the `EEx.SmartEngine`, but you can configure it to use any other engine. Examples could be `Phoenix.HTML.Engine` or `Phoenix.LiveView.Engine`.
```elixir
- config :temple, :mode, :normal # default
-
- # or
-
- config :temple, :mode, :live_view
+ config :temple, engine: Phoenix.HTML.Engine
```
### Aliases
@@ -123,16 +70,16 @@ defmodule Temple do
```elixir
config :temple, :aliases,
- label: :_label,
- link: :_link,
- select: :_select
+ label: :label_tag,
+ link: :link_tag,
+ select: :select_tag
temple do
- _label do
+ label_tag do
"Email"
end
- _link href: "/css/site.css"
+ link_tag href: "/css/site.css"
end
```
@@ -147,83 +94,20 @@ defmodule Temple do
```
"""
- defmacro __using__(_) do
- quote location: :keep do
- import Temple
- require Temple.Component
- end
- end
-
- @doc """
- Context for temple markup.
-
- Returns an EEx string that can be passed into an EEx template engine.
-
- ## Usage
-
- ```elixir
- import Temple
-
- temple do
- div class: @class do
- "Hello, world!"
- end
- end
-
- #
- # Hello, world!
- #
- ```
- """
- defmacro temple([do: block] = _block) do
- markup =
- block
- |> Parser.parse()
- |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
- |> Enum.intersperse("\n")
- |> :erlang.iolist_to_binary()
-
- quote location: :keep do
- unquote(markup)
- end
- end
-
defmacro temple(block) do
- quote location: :keep do
- unquote(block)
- |> Parser.parse()
- |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
- |> Enum.intersperse("\n")
- |> :erlang.iolist_to_binary()
+ opts = [engine: engine()]
+
+ quote do
+ require Temple.Renderer
+ Temple.Renderer.compile(unquote(opts), unquote(block))
end
end
- @doc """
- Compiles temple markup into a quoted expression using the given EEx Engine.
-
- Returns the same output that Phoenix templates output into the `render/1` function of their view modules.
-
- ## Usage
-
- ```elixir
- require Temple
-
- Temple.compile Phoenix.HTML.Engine do
- div class: @class do
- "Hello, world!"
- end
+ @doc false
+ def component(func, assigns) do
+ apply(func, [assigns])
end
- ```
- """
- defmacro compile(engine, [do: block] = _block) do
- markup =
- block
- |> Parser.parse()
- |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
- |> Enum.intersperse("\n")
- |> :erlang.iolist_to_binary()
-
- EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
- end
+ @doc false
+ def engine(), do: @engine
end
diff --git a/lib/temple/component.ex b/lib/temple/component.ex
deleted file mode 100644
index 6cf0a9e..0000000
--- a/lib/temple/component.ex
+++ /dev/null
@@ -1,274 +0,0 @@
-defmodule Temple.Component do
- @moduledoc """
- API for defining components.
-
- 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.
-
- Since component modules are view modules, the assigns you pass to the component are accessible via the `@` macro and the `assigns` variable.
-
- You must `require Temple.Component` in your views that use components, as the `c` and `slot` generate markup that uses macros provided by Temple.
-
- ## Components
-
- ```elixir
- defmodule MyAppWeb.Components.Flash do
- import 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
- slot :default
- 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.
-
- ```
- c MyAppWeb.Components.Flash, class: "font-bold", message_type: :info do
- ul do
- for info <- infos do
- li class: "p-4" do
- info.message
- end
- end
- end
- end
- ```
-
- Since components are just modules, if you alias your module, you can use them more ergonomically.
-
- ```
- # lib/my_app_web/views/page_view.ex
- alias MyAppWeb.Components.Flex
-
- # lib/my_app_web/templates/page/index.html.exs
- c Flex, class: "justify-between items center" do
- for item <- items do
- div class: "p-4" do
- item.name
- end
- end
- end
- ```
-
- ## Slots
-
- Components can use slots, which are named placeholders for markup that can be passed to the component by the caller.
-
- Slots are invoked by using the `slot` keyword, followed by the name of the slot and any assigns you'd like to pass into the slot.
-
- `slot` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
-
- ```elixir
- defmodule Flex do
- import Temple.Component
-
- render do
- div class: "flex #\{@class}" do
- slot :default
- end
- end
- end
- ```
-
- You can also use "named slots", which allow for data to be passed back into them. This is very useful
- when a component needs to pass data from the inside of the component back to the caller, like when rendering a form in LiveView.
-
- ```elixir
- defmodule Form do
- import Temple.Component
-
- render do
- form = form_for(@changeset, @action, assigns)
-
- form
-
- slot :f, form: form
-
- ""
- end
- end
- ```
-
- By default, the body of a component fills the `:default` slot.
-
- Named slots can be defined by invoking the `slot` keyword with the name of the slot and a do block.
-
- You can also pattern match on any assigns that are being passed into the slot as if you were defining an anonymous function.
-
- `slot` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
-
- ```elixir
- # lib/my_app_web/templates/post/new.html.lexs
-
- c Form, changeset: @changeset,
- action: @action,
- class: "form-control",
- phx_submit: :save,
- phx_change: :validate do
- slot :f, %{form: f} do
- label f do
- "Widget Name"
- text_input f, :name, class: "text-input"
- end
-
- submit "Save!"
- end
- end
- ```
- """
-
- @doc false
- defmacro __component__(module, assigns \\ [], block \\ []) do
- {inner_block, assigns} =
- case {block, assigns} do
- {[do: do_block], _} -> {rewrite_do(do_block), assigns}
- {_, [do: do_block]} -> {rewrite_do(do_block), []}
- {_, _} -> {nil, assigns}
- end
-
- if is_nil(inner_block) do
- quote do
- Phoenix.View.render(unquote(module), :self, unquote(assigns))
- end
- else
- quote do
- Phoenix.View.render(
- unquote(module),
- :self,
- Map.put(Map.new(unquote(assigns)), :inner_block, unquote(inner_block))
- )
- end
- end
- end
-
- @doc false
- defmacro __render_block__(inner_block, argument \\ []) do
- quote do
- unquote(inner_block).(unquote(argument))
- end
- end
-
- defp rewrite_do([{:->, meta, _} | _] = do_block) do
- {:fn, meta, do_block}
- end
-
- defp rewrite_do(do_block) do
- quote do
- fn _ ->
- unquote(do_block)
- end
- end
- end
-
- @doc """
- Defines a component template.
-
- ## Usage
-
- ```elixir
- defmodule MyAppWeb.Components.Flash do
- import 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
- slot :default
- end
- end
- end
- ```
-
- """
- defmacro render(block) do
- quote do
- def render(var!(assigns)) do
- require Temple
-
- _ = var!(assigns)
-
- Temple.compile(unquote(Temple.Component.__engine__()), unquote(block))
- end
-
- 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
- slot :default
- 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
- import 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
diff --git a/lib/temple/config.ex b/lib/temple/config.ex
deleted file mode 100644
index b34aa53..0000000
--- a/lib/temple/config.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Temple.Config do
- @moduledoc false
-
- def mode do
- case Application.get_env(:temple, :mode, :normal) do
- :normal ->
- %{
- component_function: "Temple.Component.__component__",
- render_block_function: "Temple.Component.__render_block__",
- renderer: fn module -> Macro.to_string(module) end
- }
-
- :live_view ->
- %{
- component_function: "component",
- render_block_function: "render_block",
- renderer: fn module -> "&" <> Macro.to_string(module) <> ".render/1" end
- }
- end
- end
-end
diff --git a/lib/temple/engine.ex b/lib/temple/engine.ex
deleted file mode 100644
index 3f8dafe..0000000
--- a/lib/temple/engine.ex
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule Temple.Engine do
- @behaviour Phoenix.Template.Engine
-
- @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
- require Temple
-
- template = path |> File.read!() |> Code.string_to_quoted!(file: path)
-
- Temple.temple(template)
- |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
- end
-end
diff --git a/lib/temple/generator.ex b/lib/temple/generator.ex
deleted file mode 100644
index c0f7d46..0000000
--- a/lib/temple/generator.ex
+++ /dev/null
@@ -1,5 +0,0 @@
-defprotocol Temple.Generator do
- @moduledoc false
-
- def to_eex(ast, indent \\ 0)
-end
diff --git a/lib/temple/live_view_engine.ex b/lib/temple/live_view_engine.ex
deleted file mode 100644
index 97e78f2..0000000
--- a/lib/temple/live_view_engine.ex
+++ /dev/null
@@ -1,46 +0,0 @@
-defmodule Temple.LiveViewEngine do
- @behaviour Phoenix.Template.Engine
-
- @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
- require Temple
-
- template = path |> File.read!() |> Code.string_to_quoted!(file: path)
-
- Temple.temple(template)
- |> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
- end
-end
diff --git a/lib/temple/parser.ex b/lib/temple/parser.ex
index 1ddbf05..7ef078a 100644
--- a/lib/temple/parser.ex
+++ b/lib/temple/parser.ex
@@ -36,7 +36,7 @@ defmodule Temple.Parser do
@doc """
Processes the given AST, adding the markup to the given buffer.
- Should return `:ok` if the parsing pass is over, or `{:component_applied, ast}` if the pass should be restarted.
+ Should return Temple.AST.
"""
@callback run(ast :: Macro.t()) :: ast()
@@ -58,7 +58,7 @@ defmodule Temple.Parser do
option textarea output progress meter
details summary menuitem menu
html
- ]a |> Enum.flat_map(fn el -> [el, :"#{el}!"] end)
+ ]a
@nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el ->
Keyword.get(@aliases, el, el)
@@ -125,7 +125,7 @@ defmodule Temple.Parser do
{_, false} <- {DoExpressions, DoExpressions.applicable?(ast)},
{_, false} <- {Match, Match.applicable?(ast)},
{_, false} <- {Default, Default.applicable?(ast)} do
- raise "No parsers applicable!!"
+ raise "No parsers applicable!"
else
{parser, true} ->
ast
diff --git a/lib/temple/parser/anonymous_functions.ex b/lib/temple/parser/anonymous_functions.ex
index dd11ff7..f0a41db 100644
--- a/lib/temple/parser/anonymous_functions.ex
+++ b/lib/temple/parser/anonymous_functions.ex
@@ -30,39 +30,4 @@ defmodule Temple.Parser.AnonymousFunctions do
Temple.Ast.new(__MODULE__, elixir_ast: expression, children: children)
end
-
- defimpl Temple.Generator do
- def to_eex(%{elixir_ast: {name, _, args}, children: children}, indent \\ 0) do
- {_do_and_else, args} = Temple.Parser.Utils.split_args(args)
-
- {args, {func, _, [{arrow, _, [[{arg, _, _}], _block]}]}, args2} =
- Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
-
- [
- "#{Parser.Utils.indent(indent)}<%= ",
- to_string(name),
- " ",
- Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", "),
- ", ",
- to_string(func),
- " ",
- to_string(arg),
- " ",
- to_string(arrow),
- " %>",
- "\n",
- for(child <- children, do: Temple.Generator.to_eex(child, indent + 1)),
- if Enum.any?(args2) do
- [
- "#{Parser.Utils.indent(indent)}<% end, ",
- Enum.map(args2, fn arg -> Macro.to_string(arg) end)
- |> Enum.join(", "),
- " %>"
- ]
- else
- ["#{Parser.Utils.indent(indent)}<% end %>", "\n"]
- end
- ]
- end
- end
end
diff --git a/lib/temple/parser/components.ex b/lib/temple/parser/components.ex
index b9913bb..81a0fc0 100644
--- a/lib/temple/parser/components.ex
+++ b/lib/temple/parser/components.ex
@@ -2,9 +2,7 @@ defmodule Temple.Parser.Components do
@moduledoc false
@behaviour Temple.Parser
- alias Temple.Parser
-
- defstruct module: nil, assigns: [], children: [], slots: []
+ defstruct function: nil, assigns: [], children: [], slots: []
@impl Temple.Parser
def applicable?({:c, _, _}) do
@@ -14,7 +12,7 @@ defmodule Temple.Parser.Components do
def applicable?(_), do: false
@impl Temple.Parser
- def run({:c, _meta, [component_module | args]}) do
+ def run({:c, _meta, [component_function | args]}) do
{do_and_else, args} =
args
|> Temple.Parser.Utils.split_args()
@@ -25,19 +23,19 @@ defmodule Temple.Parser.Components do
if children = do_and_else[:do] do
Macro.prewalk(
children,
- {component_module, %{}},
+ {component_function, %{}},
fn
{:c, _, [name | _]} = node, {_, named_slots} ->
{node, {name, named_slots}}
- {:slot, _, [name | args]} = node, {^component_module, named_slots} ->
+ {:slot, _, [name | args]} = node, {^component_function, named_slots} ->
{assigns, slot} = split_assigns_and_children(args, Macro.escape(%{}))
if is_nil(slot) do
- {node, {component_module, named_slots}}
+ {node, {component_function, named_slots}}
else
{nil,
- {component_module, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}}
+ {component_function, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}}
end
node, acc ->
@@ -66,7 +64,7 @@ defmodule Temple.Parser.Components do
end
Temple.Ast.new(__MODULE__,
- module: Macro.expand_once(component_module, __ENV__),
+ function: component_function,
assigns: assigns,
slots: slots,
children: children
@@ -88,43 +86,4 @@ defmodule Temple.Parser.Components do
{empty, nil}
end
end
-
- defimpl Temple.Generator do
- def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}, indent \\ 0) do
- component_function = Temple.Config.mode().component_function
- renderer = Temple.Config.mode().renderer.(module)
-
- [
- "#{Parser.Utils.indent(indent)}<%= #{component_function} ",
- renderer,
- ", ",
- Macro.to_string(assigns),
- if not Enum.empty?(children ++ slots) do
- [
- " do %>\n",
- if not Enum.empty?(children) do
- [
- "#{Parser.Utils.indent(indent + 1)}<% {:default, _} -> %>\n",
- for(child <- children, do: Temple.Generator.to_eex(child, indent + 2))
- ]
- else
- ""
- end,
- for slot <- slots do
- [
- "#{Parser.Utils.indent(indent + 1)}<% {:",
- to_string(slot.name),
- ", ",
- "#{Macro.to_string(slot.assigns)}} -> %>\n",
- for(child <- slot.content, do: Temple.Generator.to_eex(child, indent + 2))
- ]
- end,
- "\n#{Parser.Utils.indent(indent)}<% end %>"
- ]
- else
- " %>"
- end
- ]
- end
- end
end
diff --git a/lib/temple/parser/default.ex b/lib/temple/parser/default.ex
index dc6e30a..e48bf38 100644
--- a/lib/temple/parser/default.ex
+++ b/lib/temple/parser/default.ex
@@ -13,10 +13,4 @@ defmodule Temple.Parser.Default do
def run(ast) do
Temple.Ast.new(__MODULE__, elixir_ast: ast)
end
-
- defimpl Temple.Generator do
- def to_eex(%{elixir_ast: expression}, indent \\ 0) do
- ["#{Parser.Utils.indent(indent)}<%= ", Macro.to_string(expression), " %>"]
- end
- end
end
diff --git a/lib/temple/parser/do_expressions.ex b/lib/temple/parser/do_expressions.ex
index 915f79d..a842cb6 100644
--- a/lib/temple/parser/do_expressions.ex
+++ b/lib/temple/parser/do_expressions.ex
@@ -28,26 +28,4 @@ defmodule Temple.Parser.DoExpressions do
Temple.Ast.new(__MODULE__, elixir_ast: {name, meta, args}, children: [do_body, else_body])
end
-
- defimpl Temple.Generator do
- def to_eex(%{elixir_ast: expression, children: [do_body, else_body]}, indent \\ 0) do
- [
- "#{Parser.Utils.indent(indent)}<%= ",
- Macro.to_string(expression),
- " do %>",
- "\n",
- for(child <- do_body, do: Temple.Generator.to_eex(child, indent + 1))
- |> Enum.intersperse("\n"),
- if(else_body != nil,
- do: [
- "#{Parser.Utils.indent(indent)}\n<% else %>\n",
- for(child <- else_body, do: Temple.Generator.to_eex(child, indent + 1))
- |> Enum.intersperse("\n")
- ],
- else: ""
- ),
- "\n#{Parser.Utils.indent(indent)}<% end %>"
- ]
- end
- end
end
diff --git a/lib/temple/parser/element_list.ex b/lib/temple/parser/element_list.ex
index 1b1260b..55da997 100644
--- a/lib/temple/parser/element_list.ex
+++ b/lib/temple/parser/element_list.ex
@@ -14,20 +14,4 @@ defmodule Temple.Parser.ElementList do
Temple.Ast.new(__MODULE__, children: children)
end
-
- defimpl Temple.Generator do
- def to_eex(%{children: children, whitespace: whitespace}, indent \\ 0) do
- child_indent = if whitespace == :loose, do: indent + 1, else: 0
- self_indent = if whitespace == :loose, do: indent, else: 0
- whitespace = if whitespace == :tight, do: [], else: ["\n"]
-
- [
- whitespace,
- for(child <- children, do: Temple.Generator.to_eex(child, child_indent))
- |> Enum.intersperse("\n"),
- whitespace,
- Temple.Parser.Utils.indent(self_indent)
- ]
- end
- end
end
diff --git a/lib/temple/parser/empty.ex b/lib/temple/parser/empty.ex
index 9a76156..a8f7bc2 100644
--- a/lib/temple/parser/empty.ex
+++ b/lib/temple/parser/empty.ex
@@ -14,10 +14,4 @@ defmodule Temple.Parser.Empty do
def run(_ast) do
Temple.Ast.new(__MODULE__)
end
-
- defimpl Temple.Generator do
- def to_eex(_, _ \\ 0) do
- []
- end
- end
end
diff --git a/lib/temple/parser/match.ex b/lib/temple/parser/match.ex
index 15a65f4..b1a794e 100644
--- a/lib/temple/parser/match.ex
+++ b/lib/temple/parser/match.ex
@@ -17,10 +17,4 @@ defmodule Temple.Parser.Match do
def run(macro) do
Temple.Ast.new(__MODULE__, elixir_ast: macro)
end
-
- defimpl Temple.Generator do
- def to_eex(%{elixir_ast: elixir_ast}, indent \\ 0) do
- ["#{Parser.Utils.indent(indent)}<% ", Macro.to_string(elixir_ast), " %>"]
- end
- end
end
diff --git a/lib/temple/parser/nonvoid_elements_aliases.ex b/lib/temple/parser/nonvoid_elements_aliases.ex
index 28bca16..faee2b6 100644
--- a/lib/temple/parser/nonvoid_elements_aliases.ex
+++ b/lib/temple/parser/nonvoid_elements_aliases.ex
@@ -14,7 +14,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
def applicable?(_), do: false
@impl Parser
- def run({name, _, args}) do
+ def run({name, meta, args}) do
name = Parser.nonvoid_elements_lookup()[name]
{do_and_else, args} = Temple.Parser.Utils.split_args(args)
@@ -26,34 +26,20 @@ defmodule Temple.Parser.NonvoidElementsAliases do
Temple.Ast.new(__MODULE__,
name: to_string(name) |> String.replace_suffix("!", ""),
attrs: args,
+ meta: %{whitespace: whitespace(meta)},
children:
Temple.Ast.new(Temple.Parser.ElementList,
children: children,
- whitespace: whitespace(to_string(name))
+ whitespace: whitespace(meta)
)
)
end
- defp whitespace(name) do
- if String.ends_with?(name, "!") do
- :tight
- else
+ defp whitespace(meta) do
+ if Keyword.has_key?(meta, :end) do
:loose
- end
- end
-
- defimpl Temple.Generator do
- def to_eex(%{name: name, attrs: attrs, children: children}, indent \\ 0) do
- [
- "#{Parser.Utils.indent(indent)}<",
- name,
- Temple.Parser.Utils.compile_attrs(attrs),
- ">",
- Temple.Generator.to_eex(children, indent),
- "",
- name,
- ">"
- ]
+ else
+ :tight
end
end
end
diff --git a/lib/temple/parser/right_arrow.ex b/lib/temple/parser/right_arrow.ex
index 3be2015..9af20d1 100644
--- a/lib/temple/parser/right_arrow.ex
+++ b/lib/temple/parser/right_arrow.ex
@@ -11,20 +11,9 @@ defmodule Temple.Parser.RightArrow do
def applicable?(_), do: false
@impl Parser
- def run({_, _, [[pattern], args]}) do
+ def run({func, meta, [pattern, args]}) do
children = Parser.parse(args)
- Temple.Ast.new(__MODULE__, elixir_ast: pattern, children: children)
- end
-
- defimpl Temple.Generator do
- def to_eex(%{elixir_ast: elixir_ast, children: children}, indent \\ 0) do
- [
- "#{Parser.Utils.indent(indent)}<% ",
- Macro.to_string(elixir_ast),
- " -> %>\n",
- for(child <- children, do: Temple.Generator.to_eex(child, indent + 1))
- ]
- end
+ Temple.Ast.new(__MODULE__, elixir_ast: {func, meta, [pattern]}, children: children)
end
end
diff --git a/lib/temple/parser/slot.ex b/lib/temple/parser/slot.ex
index 287992d..426899a 100644
--- a/lib/temple/parser/slot.ex
+++ b/lib/temple/parser/slot.ex
@@ -1,7 +1,6 @@
defmodule Temple.Parser.Slot do
@moduledoc false
@behaviour Temple.Parser
- alias Temple.Parser.Utils
defstruct name: nil, args: []
@@ -25,18 +24,4 @@ defmodule Temple.Parser.Slot do
Temple.Ast.new(__MODULE__, name: slot_name, args: args)
end
-
- defimpl Temple.Generator do
- def to_eex(%{name: name, args: args}, indent \\ 0) do
- render_block_function = Temple.Config.mode().render_block_function
-
- [
- "#{Utils.indent(indent)}<%= #{render_block_function}(@inner_block, {:",
- to_string(name),
- ", ",
- Macro.to_string(quote(do: Enum.into(unquote(args), %{}))),
- "}) %>\n"
- ]
- end
- end
end
diff --git a/lib/temple/parser/text.ex b/lib/temple/parser/text.ex
index 3642d5c..cfc50da 100644
--- a/lib/temple/parser/text.ex
+++ b/lib/temple/parser/text.ex
@@ -14,10 +14,4 @@ defmodule Temple.Parser.Text do
def run(text) do
Temple.Ast.new(__MODULE__, text: text)
end
-
- defimpl Temple.Generator do
- def to_eex(%{text: text}, indent \\ 0) do
- [Parser.Utils.indent(indent), text]
- end
- end
end
diff --git a/lib/temple/parser/utils.ex b/lib/temple/parser/utils.ex
index bbe05e3..cae7523 100644
--- a/lib/temple/parser/utils.ex
+++ b/lib/temple/parser/utils.ex
@@ -7,7 +7,7 @@ defmodule Temple.Parser.Utils do
def kebab_to_snake(stringable),
do: stringable |> to_string() |> String.replace("-", "_")
- def compile_attrs([]), do: ""
+ def compile_attrs([]), do: []
def compile_attrs([attrs]) when is_list(attrs) do
compile_attrs(attrs)
@@ -15,49 +15,54 @@ defmodule Temple.Parser.Utils do
def compile_attrs(attrs) when is_list(attrs) do
if Keyword.keyword?(attrs) do
- for {name, value} <- attrs, into: "" do
- name = snake_to_kebab(name)
+ for {name, value} <- attrs, reduce: [] do
+ acc ->
+ name = snake_to_kebab(name)
- with false <- not is_binary(value) && Macro.quoted_literal?(value),
- false <- match?({_, _, _}, value),
- false <- is_list(value) do
- " " <> name <> "=\"" <> to_string(value) <> "\""
- else
- true ->
- ~s|<%= {:safe, Temple.Parser.Utils.build_attr("#{name}", #{Macro.to_string(value)})} %>|
- end
+ with false <- not is_binary(value) && Macro.quoted_literal?(value),
+ false <- match?({_, _, _}, value),
+ false <- is_list(value) do
+ [{:text, " " <> name <> "=\"" <> to_string(value) <> "\""} | acc]
+ else
+ true ->
+ nodes = Temple.Parser.Utils.build_attr(name, value)
+ Enum.reverse(nodes) ++ acc
+ end
end
+ |> Enum.reverse()
else
- "<%= Temple.Parser.Utils.runtime_attrs(" <>
- (attrs |> List.first() |> Macro.to_string()) <> ") %>"
+ [
+ {:expr,
+ quote do
+ unquote(List.first(attrs))
+ end}
+ ]
end
end
- def runtime_attrs(attrs) do
- {:safe,
- for {name, value} <- attrs, name not in [:inner_block, :inner_content], into: "" do
- name = snake_to_kebab(name)
-
- build_attr(name, value)
- end}
- end
-
def build_attr(name, true) do
- " " <> name
+ [{:text, " " <> name}]
end
def build_attr(_name, false) do
- ""
+ []
+ end
+
+ def build_attr(name, {_, _, _} = value) do
+ [{:text, ~s' #{name}="'}, {:expr, value}, {:text, ~s'"'}]
end
def build_attr("class", classes) when is_list(classes) do
- value = String.trim_leading(for {class, true} <- classes, into: "", do: " #{class}")
+ value =
+ quote do
+ String.trim_leading(for {class, true} <- unquote(classes), into: "", do: " #{class}")
+ end
- " class" <> "=\"" <> value <> "\""
+ [{:text, ~s' class="'}, {:expr, value}, {:text, ~s'"'}]
end
def build_attr(name, value) do
- " " <> name <> "=\"" <> to_string(value) <> "\""
+ [{:text, ~s' #{name}="' <> to_string(value) <> ~s'"'}]
end
def split_args(not_what_i_want) when is_nil(not_what_i_want) or is_atom(not_what_i_want),
@@ -114,7 +119,19 @@ defmodule Temple.Parser.Utils do
Keyword.pop(args, :compact, false)
end
+ def indent(nil) do
+ ""
+ end
+
def indent(level) do
String.duplicate(" ", level * 2)
end
+
+ def inspect_ast(ast) do
+ ast
+ |> Macro.to_string()
+ |> IO.puts()
+
+ ast
+ end
end
diff --git a/lib/temple/parser/void_elements_aliases.ex b/lib/temple/parser/void_elements_aliases.ex
index 71ae88d..22ccc74 100644
--- a/lib/temple/parser/void_elements_aliases.ex
+++ b/lib/temple/parser/void_elements_aliases.ex
@@ -2,8 +2,6 @@ defmodule Temple.Parser.VoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser
- alias Temple.Parser.Utils
-
defstruct name: nil, attrs: []
@impl Temple.Parser
@@ -28,15 +26,4 @@ defmodule Temple.Parser.VoidElementsAliases do
Temple.Ast.new(__MODULE__, name: name, attrs: args)
end
-
- defimpl Temple.Generator do
- def to_eex(%{name: name, attrs: attrs}, indent \\ 0) do
- [
- "#{Utils.indent(indent)}<",
- to_string(name),
- Temple.Parser.Utils.compile_attrs(attrs),
- ">"
- ]
- end
- end
end
diff --git a/lib/temple/renderer.ex b/lib/temple/renderer.ex
new file mode 100644
index 0000000..4538dc0
--- /dev/null
+++ b/lib/temple/renderer.ex
@@ -0,0 +1,361 @@
+defmodule Temple.Renderer do
+ @moduledoc false
+
+ alias Temple.Parser.ElementList
+ alias Temple.Parser.Text
+ alias Temple.Parser.Components
+ alias Temple.Parser.Slot
+ alias Temple.Parser.NonvoidElementsAliases
+ alias Temple.Parser.VoidElementsAliases
+ alias Temple.Parser.AnonymousFunctions
+ alias Temple.Parser.RightArrow
+ alias Temple.Parser.DoExpressions
+ alias Temple.Parser.Match
+ alias Temple.Parser.Default
+ alias Temple.Parser.Empty
+
+ alias Temple.Parser.Utils
+
+ @default_engine EEx.SmartEngine
+
+ defmacro compile(opts \\ [], do: block) do
+ block
+ |> Temple.Parser.parse()
+ |> Temple.Renderer.render(opts)
+
+ # |> Temple.Parser.Utils.inspect_ast()
+ end
+
+ def render(asts, opts \\ [])
+
+ def render(asts, opts) when is_list(asts) and is_list(opts) do
+ engine = Keyword.get(opts, :engine, @default_engine)
+
+ state = %{
+ engine: engine,
+ indentation: 0,
+ terminal_node: false
+ }
+
+ buffer = engine.init(%{})
+
+ buffer =
+ for ast <- asts, reduce: buffer do
+ buffer ->
+ render(buffer, state, ast)
+ end
+
+ if function_exported?(engine, :handle_body, 2) do
+ engine.handle_body(buffer, root: length(asts) == 1)
+ else
+ engine.handle_body(buffer)
+ end
+ end
+
+ def render(buffer, state, %Text{text: text}) do
+ t = Utils.indent(state.indentation) <> text <> new_line(state)
+
+ unless t == "" do
+ state.engine.handle_text(
+ buffer,
+ [],
+ t
+ )
+ end
+ end
+
+ def render(buffer, state, asts) when is_list(asts) do
+ for ast <- asts, reduce: buffer do
+ buffer ->
+ render(buffer, state, ast)
+ end
+ end
+
+ def render(buffer, state, %Components{
+ function: function,
+ assigns: assigns,
+ children: children,
+ slots: slots
+ }) do
+ child_quoted =
+ if Enum.any?(children) do
+ children_buffer = state.engine.handle_begin(buffer)
+
+ children_buffer =
+ for child <- children(children), reduce: children_buffer do
+ children_buffer ->
+ render(children_buffer, state, child)
+ end
+
+ state.engine.handle_end(children_buffer)
+ end
+
+ slot_quotes =
+ for slot <- slots do
+ slot_buffer = state.engine.handle_begin(buffer)
+
+ slot_buffer =
+ for child <- children(slot.content), reduce: slot_buffer do
+ slot_buffer ->
+ render(slot_buffer, state, child)
+ end
+
+ ast = state.engine.handle_end(slot_buffer)
+
+ [quoted] =
+ quote do
+ {unquote(slot.name), unquote(slot.assigns)} ->
+ unquote(ast)
+ end
+
+ quoted
+ end
+
+ {:fn, meta, clauses} =
+ quote do
+ fn
+ {:default, _} -> unquote(child_quoted)
+ end
+ end
+
+ slot_func = {:fn, meta, slot_quotes ++ clauses}
+
+ expr =
+ quote do
+ component(
+ unquote(function),
+ Map.put(Map.new(unquote(assigns)), :__slots__, unquote(slot_func))
+ )
+ end
+
+ state.engine.handle_expr(buffer, "=", expr)
+ end
+
+ def render(buffer, state, %Slot{} = ast) do
+ render_slot_func =
+ quote do
+ var!(assigns).__slots__.({unquote(ast.name), Map.new(unquote(ast.args))})
+ end
+
+ state.engine.handle_expr(buffer, "=", render_slot_func)
+ end
+
+ def render(buffer, state, %ElementList{} = ast) do
+ render(buffer, state, ast.children)
+ end
+
+ def render(buffer, state, %NonvoidElementsAliases{} = ast) do
+ current_indent = Utils.indent(state.indentation)
+
+ inside_new_lines = if ast.meta.whitespace == :tight, do: "", else: "\n"
+ new_indent = if ast.meta.whitespace == :tight, do: nil, else: state.indentation + 1
+
+ buffer =
+ state.engine.handle_text(
+ buffer,
+ [],
+ "#{current_indent}<#{ast.name}"
+ )
+
+ buffer =
+ for node <- Utils.compile_attrs(ast.attrs), reduce: buffer do
+ buffer ->
+ case node do
+ {:text, text} ->
+ state.engine.handle_text(buffer, [], text)
+
+ {:expr, expr} ->
+ state.engine.handle_expr(buffer, "=", expr)
+ end
+ end
+
+ buffer =
+ state.engine.handle_text(
+ buffer,
+ [],
+ ">#{inside_new_lines}"
+ )
+
+ buffer =
+ if Enum.any?(children(ast.children)) do
+ for {child, index} <- Enum.with_index(children(ast.children), 1), reduce: buffer do
+ buffer ->
+ render(
+ buffer,
+ %{
+ state
+ | indentation: new_indent,
+ terminal_node: index == length(children(ast.children))
+ },
+ child
+ )
+ end
+ else
+ buffer
+ end
+
+ state.engine.handle_text(
+ buffer,
+ [],
+ "#{inside_new_lines}#{Utils.indent(if(ast.meta.whitespace == :loose, do: state.indentation, else: nil))}#{ast.name}>#{new_line(state)}\n"
+ )
+ end
+
+ def render(buffer, state, %VoidElementsAliases{} = ast) do
+ current_indent = Utils.indent(state.indentation)
+
+ buffer =
+ state.engine.handle_text(
+ buffer,
+ [],
+ "#{current_indent}<#{ast.name}"
+ )
+
+ buffer =
+ for node <- Utils.compile_attrs(ast.attrs), reduce: buffer do
+ buffer ->
+ case node do
+ {:text, text} ->
+ state.engine.handle_text(buffer, [], text)
+
+ {:expr, expr} ->
+ state.engine.handle_expr(buffer, "=", expr)
+ end
+ end
+
+ state.engine.handle_text(buffer, [], ">\n")
+ end
+
+ def render(buffer, state, %AnonymousFunctions{} = ast) do
+ new_buffer = state.engine.handle_begin(buffer)
+
+ new_buffer =
+ for child <- children(ast.children), child != nil, reduce: new_buffer do
+ new_buffer ->
+ render(new_buffer, state, child)
+ end
+
+ new_buffer = state.engine.handle_text(new_buffer, [], "\n")
+
+ inner_quoted = state.engine.handle_end(new_buffer)
+
+ {name, meta, args} = ast.elixir_ast
+
+ {args, {func, fmeta, [{arrow, arrowmeta, [first, _block]}]}, args2} =
+ Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
+
+ full_ast =
+ {name, meta, args ++ [{func, fmeta, [{arrow, arrowmeta, [first, inner_quoted]}]}] ++ args2}
+
+ state.engine.handle_expr(buffer, "=", full_ast)
+ end
+
+ def render(buffer, state, %RightArrow{elixir_ast: elixir_ast} = ast) do
+ new_buffer = state.engine.handle_begin(buffer)
+
+ new_buffer =
+ for child <- children(ast.children), child != nil, reduce: new_buffer do
+ new_buffer ->
+ render(new_buffer, state, child)
+ end
+
+ inner_quoted = state.engine.handle_end(new_buffer)
+
+ {func, meta, [first]} = elixir_ast
+
+ full_ast = {func, meta, [first | [inner_quoted]]}
+
+ state.engine.handle_expr(buffer, "", full_ast)
+ end
+
+ def render(buffer, state, %DoExpressions{} = ast) do
+ {func, meta, args} = ast.elixir_ast
+ new_buffer = state.engine.handle_begin(buffer)
+
+ [do_block, else_block] = ast.children
+
+ do_inner_quoted =
+ case do_block do
+ [%RightArrow{} | _] = bodies ->
+ for b <- bodies do
+ new_buffer = state.engine.handle_begin(buffer)
+
+ new_buffer = render(new_buffer, state, b)
+
+ {:__block__, _, [quoted | _]} = state.engine.handle_end(new_buffer)
+ quoted
+ end
+
+ block ->
+ for child <- children(block), child != nil, reduce: new_buffer do
+ new_buffer ->
+ render(new_buffer, state, child)
+ end
+ |> state.engine.handle_end()
+ end
+
+ else_inner_quoted =
+ if else_block do
+ case else_block do
+ [%RightArrow{} | _] = bodies ->
+ for b <- bodies do
+ new_buffer = state.engine.handle_begin(buffer)
+
+ new_buffer = render(new_buffer, state, b)
+
+ {:__block__, _, [quoted | _]} = state.engine.handle_end(new_buffer)
+ quoted
+ end
+
+ block ->
+ for child <- children(block), child != nil, reduce: new_buffer do
+ new_buffer ->
+ render(new_buffer, state, child)
+ end
+ |> state.engine.handle_end()
+ end
+ end
+
+ new_args =
+ then([do: do_inner_quoted], fn args ->
+ if else_inner_quoted do
+ Keyword.put(args, :else, else_inner_quoted) |> Enum.reverse()
+ else
+ args
+ end
+ end)
+
+ full_ast = {func, meta, args ++ [new_args]}
+
+ state.engine.handle_expr(buffer, "=", full_ast)
+ end
+
+ def render(buffer, state, %Match{elixir_ast: elixir_ast}) do
+ state.engine.handle_expr(buffer, "", elixir_ast)
+ end
+
+ def render(buffer, state, %Default{elixir_ast: elixir_ast}) do
+ buffer =
+ if state.indentation && state.indentation > 0 do
+ state.engine.handle_text(buffer, [], Utils.indent(state.indentation))
+ else
+ buffer
+ end
+
+ buffer = state.engine.handle_expr(buffer, "=", elixir_ast)
+
+ if not state.terminal_node do
+ state.engine.handle_text(buffer, [], "\n")
+ else
+ buffer
+ end
+ end
+
+ def render(buffer, _state, %Empty{}), do: buffer
+
+ defp children(%ElementList{children: children}), do: children
+ defp children(list) when is_list(list), do: list
+
+ def new_line(%{terminal_node: false}), do: "\n"
+ def new_line(%{terminal_node: true}), do: ""
+end
diff --git a/mix.exs b/mix.exs
index 0d8e293..90312aa 100644
--- a/mix.exs
+++ b/mix.exs
@@ -6,17 +6,14 @@ defmodule Temple.MixProject do
app: :temple,
name: "Temple",
description: "An HTML DSL for Elixir and Phoenix",
- version: "0.8.0",
+ version: "0.9.0-rc.0",
package: package(),
elixirc_paths: elixirc_paths(Mix.env()),
- elixir: "~> 1.9",
+ elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps(),
source_url: "https://github.com/mhanberg/temple",
- docs: [
- main: "Temple",
- extras: ["README.md"]
- ]
+ docs: docs()
]
end
@@ -31,6 +28,27 @@ defmodule Temple.MixProject do
]
end
+ defp docs() do
+ [
+ main: "Temple",
+ extras: [
+ "README.md",
+ "guides/getting-started.md",
+ "guides/your-first-template.md",
+ "guides/components.md",
+ "guides/migrating/0.8-to-0.9.md"
+ ],
+ groups_for_extras: groups_for_extras()
+ ]
+ end
+
+ defp groups_for_extras do
+ [
+ Guides: ~r/guides\/[^\/]+\.md/,
+ Migrating: ~r/guides\/migrating\/.?/
+ ]
+ end
+
defp package do
[
maintainers: ["Mitchell Hanberg"],
@@ -43,8 +61,8 @@ defmodule Temple.MixProject do
defp deps do
[
{:ex_doc, "~> 0.28.3", only: :dev, runtime: false},
- {:phoenix_view, "~> 1.0"},
- {:phoenix_live_view, ">= 0.0.0", only: :test}
+ {:phoenix_view, "~> 1.1.2", optional: true},
+ {:phoenix, "~> 1.6", optional: true}
]
end
end
diff --git a/mix.lock b/mix.lock
index 6fbc162..5c3a4e5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -6,12 +6,10 @@
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
- "phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"},
- "phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.5", "63f52a6f9f6983f04e424586ff897c016ecc5e4f8d1e2c22c2887af1c57215d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5586e6a3d4df71b8214c769d4f5eb8ece2b4001711a7ca0f97323c36958b0e3"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
- "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
- "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
+ "phoenix": {:hex, :phoenix, "1.6.7", "f1de32418bbbcd471f4fe74d3860ee9c8e8c6c36a0ec173be8ff468a5d72ac90", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b354a4f11d9a2f3a380fb731042dae064f22d7aed8c7e7c024a2459f12994aad"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
+ "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
+ "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
- "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
+ "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
}
diff --git a/temple-github-image.png b/temple-github-image.png
new file mode 100644
index 0000000..fcb01a6
Binary files /dev/null and b/temple-github-image.png differ
diff --git a/test/component_test.exs b/test/component_test.exs
deleted file mode 100644
index 9750f31..0000000
--- a/test/component_test.exs
+++ /dev/null
@@ -1,160 +0,0 @@
-defmodule Temple.ComponentTest do
- use ExUnit.Case, async: true
- use Temple
- use Temple.Support.Utils
-
- 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 evaluate_template(result) ==
- ~s"""
-
- Hello, world
-
-
-
-
-
-
-
- """
- 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 evaluate_template(result) ==
- ~s"""
-
- Hello, world
-
-
-
- I'm a component!
-
-
- """
- 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 evaluate_template(result) == ~s"""
-
-
-
- outer!
-
-
-
-
-
-
-
- inner!
-
-
-
- """
- 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 evaluate_template(result) == ~s"""
-
-
- doo doo
-
-
-
- """
- end
-
- test "components can be void elements" do
- result =
- temple do
- c Temple.Components.VoidComponent, foo: :bar
- end
-
- assert evaluate_template(result) == ~s"""
-
- bar
-
- """
- end
-
- test "components can have named slots" do
- assigns = %{name: "bob"}
-
- result =
- temple do
- c Temple.Components.WithSlot do
- slot :header, %{value: val} do
- div do
- "the value is #{val}"
- end
- end
-
- button class: "btn", phx_click: :toggle do
- @name
- end
- end
- end
-
- assert evaluate_template(result, assigns) ==
- ~s"""
-
-
-
- the value is Header
-
-
-
-
-
-
-
-
-
- """
- end
-end
diff --git a/test/parser/anonymous_functions_test.exs b/test/parser/anonymous_functions_test.exs
index 6564304..d100711 100644
--- a/test/parser/anonymous_functions_test.exs
+++ b/test/parser/anonymous_functions_test.exs
@@ -64,24 +64,4 @@ defmodule Temple.Parser.AnonymousFunctionsTest do
} = ast
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex" do
- raw_ast =
- quote do
- form_for(changeset, Routes.foo_path(conn, :create), fn form ->
- Does.something!(form)
- end)
- end
-
- result =
- raw_ast
- |> AnonymousFunctions.run()
- |> struct(children: [])
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s|<%= form_for changeset, Routes.foo_path(conn, :create), fn form -> %>\n<% end %>\n|
- end
- end
end
diff --git a/test/parser/components_test.exs b/test/parser/components_test.exs
index 224f87e..88a1754 100644
--- a/test/parser/components_test.exs
+++ b/test/parser/components_test.exs
@@ -2,13 +2,12 @@ defmodule Temple.Parser.ComponentsTest do
use ExUnit.Case, async: false
alias Temple.Parser.Components
alias Temple.Parser.Slottable
- 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
+ c &SomeModule.render/1, foo: :bar do
div do
"hello"
end
@@ -21,7 +20,9 @@ defmodule Temple.Parser.ComponentsTest do
test "runs when using the `c` ast with an inline block" do
ast =
quote do
- c SomeModule, foo: :bar, do: "hello"
+ c &SomeModule.render/1, foo: :bar do
+ "hello"
+ end
end
assert Components.applicable?(ast)
@@ -30,7 +31,7 @@ defmodule Temple.Parser.ComponentsTest do
test "runs when using the `c` ast without a block" do
ast =
quote do
- c(SomeModule, foo: :bar)
+ c &SomeModule.render/1, foo: :bar
end
assert Components.applicable?(ast)
@@ -38,10 +39,14 @@ defmodule Temple.Parser.ComponentsTest do
end
describe "run/2" do
- test "adds a node to the buffer" do
+ setup do
+ [func: quote(do: &SomeModule.render/1)]
+ end
+
+ test "adds a node to the buffer", %{func: func} do
raw_ast =
quote do
- c SomeModule do
+ c unquote(func) do
aside class: "foobar" do
"I'm a component!"
end
@@ -51,31 +56,31 @@ defmodule Temple.Parser.ComponentsTest do
ast = Components.run(raw_ast)
assert %Components{
- module: SomeModule,
+ function: ^func,
assigns: [],
children: _
} = ast
end
- test "runs when using the `c` ast with an inline block" do
+ test "runs when using the `c` ast with an inline block", %{func: func} do
ast =
quote do
- c SomeModule, foo: :bar, do: "hello"
+ c unquote(func), foo: :bar, do: "hello"
end
ast = Components.run(ast)
assert %Components{
- module: SomeModule,
+ function: ^func,
assigns: [foo: :bar],
children: _
} = ast
end
- test "adds a node to the buffer that takes args" do
+ test "adds a node to the buffer that takes args", %{func: func} do
raw_ast =
quote do
- c SomeModule, foo: :bar do
+ c unquote(func), foo: :bar do
aside class: "foobar" do
"I'm a component!"
end
@@ -85,31 +90,31 @@ defmodule Temple.Parser.ComponentsTest do
ast = Components.run(raw_ast)
assert %Components{
- module: SomeModule,
+ function: ^func,
assigns: [foo: :bar],
children: _
} = ast
end
- test "adds a node to the buffer that without a block" do
+ test "adds a node to the buffer that without a block", %{func: func} do
raw_ast =
quote do
- c SomeModule, foo: :bar
+ c unquote(func), foo: :bar
end
ast = Components.run(raw_ast)
assert %Components{
- module: SomeModule,
+ function: ^func,
assigns: [foo: :bar],
children: []
} = ast
end
- test "gathers all slots" do
+ test "gathers all slots", %{func: func} do
raw_ast =
quote do
- c SomeModule, foo: :bar do
+ c unquote(func), foo: :bar do
slot :foo, %{form: form} do
"in the slot"
end
@@ -119,7 +124,7 @@ defmodule Temple.Parser.ComponentsTest do
ast = Components.run(raw_ast)
assert %Components{
- module: SomeModule,
+ function: ^func,
assigns: [foo: :bar],
slots: [
%Slottable{
@@ -133,11 +138,15 @@ defmodule Temple.Parser.ComponentsTest do
end
test "slots should only be assigned to the component root" do
+ card = quote do: &Card.render/1
+ footer = quote do: &Card.Footer.render/1
+ list = quote do: &LinkList.render/1
+
raw_ast =
quote do
- c Card do
- c Card.Footer do
- c LinkList, socials: @user.socials do
+ c unquote(card) do
+ c unquote(footer) do
+ c unquote(list), socials: @user.socials do
"hello"
slot :default, %{text: text, url: url} do
@@ -171,107 +180,4 @@ defmodule Temple.Parser.ComponentsTest do
} = ast
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex for non void component" do
- raw_ast =
- quote do
- c SomeModule, foo: :bar do
- "I'm a component!"
- end
- end
-
- result =
- raw_ast
- |> Components.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s"""
- <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>
- <% {:default, _} -> %>
- I'm a component!
- <% end %>
- """
- end
-
- test "emits eex for void component with slots" do
- raw_ast =
- quote do
- c SomeModule, foo: :bar do
- slot :foo, %{form: form} do
- div do
- "in the slot"
- end
- end
- end
- end
-
- result =
- raw_ast
- |> Components.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s"""
- <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>
- <% {:foo, %{form: form}} -> %>
-
- in the slot
-
- <% end %>
- """
- end
-
- test "emits eex for nonvoid component with slots" do
- raw_ast =
- quote do
- c SomeModule, foo: :bar do
- slot :foo, %{form: form} do
- div do
- "in the slot"
- end
- end
-
- div do
- "inner content"
- end
- end
- end
-
- result =
- raw_ast
- |> Components.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s"""
- <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>
- <% {:default, _} -> %>
-
- inner content
-
- <% {:foo, %{form: form}} -> %>
-
- in the slot
-
- <% end %>
- """
- end
-
- test "emits eex for void component" do
- raw_ast =
- quote do
- c SomeModule, foo: :bar
- end
-
- result =
- raw_ast
- |> Components.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] %>|
- end
- end
end
diff --git a/test/parser/default_test.exs b/test/parser/default_test.exs
index 7c2ffd8..3f2aa99 100644
--- a/test/parser/default_test.exs
+++ b/test/parser/default_test.exs
@@ -2,7 +2,6 @@ defmodule Temple.Parser.DefaultTest do
use ExUnit.Case, async: true
alias Temple.Parser.Default
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is an elixir expression" do
@@ -27,20 +26,4 @@ defmodule Temple.Parser.DefaultTest do
assert %Default{elixir_ast: expression} == ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- Foo.bar!(baz)
- end
- |> Default.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result == ~s"""
- <%= Foo.bar!(baz) %>
- """
- end
- end
end
diff --git a/test/parser/do_expressions_test.exs b/test/parser/do_expressions_test.exs
index 6592fc5..7bde960 100644
--- a/test/parser/do_expressions_test.exs
+++ b/test/parser/do_expressions_test.exs
@@ -2,7 +2,6 @@ defmodule Temple.Parser.DoExpressionsTest do
use ExUnit.Case, async: true
alias Temple.Parser.DoExpressions
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node contains a do expression" do
@@ -37,72 +36,4 @@ defmodule Temple.Parser.DoExpressionsTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- for big <- boys do
- "bob"
- end
- end
- |> DoExpressions.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
- <%= for(big <- boys) do %>
- bob
- <% end %>
- """
- end
-
- test "emits eex for that includes in else clause" do
- result =
- quote do
- if foo? do
- "bob"
-
- "bobby"
- else
- "carol"
- end
- end
- |> DoExpressions.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
- <%= if(foo?) do %>
- bob
- bobby
- <% else %>
- carol
- <% end %>
- """
- end
-
- test "emits eex for a case expression" do
- result =
- quote do
- case foo? do
- :bing ->
- :bong
- end
- end
- |> DoExpressions.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
- <%= case(foo?) do %>
- <% :bing -> %>
- <%= :bong %>
- <% end %>
- """
- end
- end
end
diff --git a/test/parser/empty_test.exs b/test/parser/empty_test.exs
index c6f492c..b727786 100644
--- a/test/parser/empty_test.exs
+++ b/test/parser/empty_test.exs
@@ -30,20 +30,4 @@ defmodule Temple.Parser.EmptyTest do
end
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex for non void component" do
- raw_ast =
- quote do
- nil
- end
-
- result =
- raw_ast
- |> Empty.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() == ""
- end
- end
end
diff --git a/test/parser/match_test.exs b/test/parser/match_test.exs
index a7cb392..10bad4f 100644
--- a/test/parser/match_test.exs
+++ b/test/parser/match_test.exs
@@ -36,37 +36,4 @@ defmodule Temple.Parser.MatchTest do
assert %Match{elixir_ast: expression} == ast
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex" do
- raw_ast =
- quote do
- yolo = :synergy
- end
-
- result =
- raw_ast
- |> Match.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() == ~s|<% yolo = :synergy %>|
- end
-
- test "emits eex big boy" do
- raw_ast =
- quote do
- yolo =
- if true do
- :synergy
- end
- end
-
- result =
- raw_ast
- |> Match.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() == ~s|<% yolo = if(true) do\n :synergy\nend %>|
- end
- end
end
diff --git a/test/parser/nonvoid_elements_aliases_test.exs b/test/parser/nonvoid_elements_aliases_test.exs
index 22411a2..7965d03 100644
--- a/test/parser/nonvoid_elements_aliases_test.exs
+++ b/test/parser/nonvoid_elements_aliases_test.exs
@@ -3,7 +3,6 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
alias Temple.Parser.NonvoidElementsAliases
alias Temple.Parser.ElementList
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is a nonvoid element or alias" do
@@ -87,59 +86,4 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- div class: "foo", id: var do
- select__ do
- option do
- "foo"
- end
- end
- end
- end
- |> NonvoidElementsAliases.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
-
>
-
-
- """
- end
-
- test "produce 'tight' markup" do
- result =
- quote do
- div class: "foo", id: var do
- select__ do
- option! do
- "foo"
- end
- end
- end
- end
- |> NonvoidElementsAliases.run()
- |> Temple.Generator.to_eex()
- |> :erlang.iolist_to_binary()
- |> Kernel.<>("\n")
-
- assert result ==
- ~s"""
-
>
-
-
- """
- end
- end
end
diff --git a/test/parser/right_arrow_test.exs b/test/parser/right_arrow_test.exs
index 7ee66ef..2c4dc4d 100644
--- a/test/parser/right_arrow_test.exs
+++ b/test/parser/right_arrow_test.exs
@@ -2,7 +2,6 @@ defmodule Temple.Parser.RightArrowTest do
use ExUnit.Case, async: true
alias Temple.Parser.RightArrow
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node contains a right arrow" do
@@ -51,7 +50,7 @@ defmodule Temple.Parser.RightArrowTest do
ast = RightArrow.run(raw_ast)
assert %RightArrow{
- elixir_ast: :bing,
+ elixir_ast: {:->, [newlines: 1], [[:bing]]},
children: [
%Temple.Parser.Default{
elixir_ast: ^bong
@@ -60,24 +59,4 @@ defmodule Temple.Parser.RightArrowTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- :bing ->
- :bong
- end
- |> List.first()
- |> RightArrow.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
- <% :bing -> %>
- <%= :bong %>
- """
- end
- end
end
diff --git a/test/parser/slot_test.exs b/test/parser/slot_test.exs
index 78b015c..9a88e4c 100644
--- a/test/parser/slot_test.exs
+++ b/test/parser/slot_test.exs
@@ -28,23 +28,4 @@ defmodule Temple.Parser.SlotTest do
} == ast
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex for a slot" do
- raw_ast =
- quote do
- slot :header, value: Form.form_for(changeset, action)
- end
-
- result =
- raw_ast
- |> Slot.run()
- |> Temple.Generator.to_eex()
-
- assert result |> :erlang.iolist_to_binary() ==
- ~s"""
- <%= Temple.Component.__render_block__(@inner_block, {:header, Enum.into([value: Form.form_for(changeset, action)], %{})}) %>
- """
- end
- end
end
diff --git a/test/parser/temple_namespace_nonvoid_test.exs b/test/parser/temple_namespace_nonvoid_test.exs
index 81df4ab..9d72d5e 100644
--- a/test/parser/temple_namespace_nonvoid_test.exs
+++ b/test/parser/temple_namespace_nonvoid_test.exs
@@ -3,7 +3,6 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
alias Temple.Parser.NonvoidElementsAliases
alias Temple.Parser.TempleNamespaceNonvoid
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is a Temple aliased nonvoid element" do
@@ -58,25 +57,4 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- Temple.div class: "foo", id: var do
- "foo"
- end
- end
- |> TempleNamespaceNonvoid.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result ==
- ~s"""
-
>
- foo
-
- """
- end
- end
end
diff --git a/test/parser/temple_namespace_void_test.exs b/test/parser/temple_namespace_void_test.exs
index 19e8d2d..d848f4b 100644
--- a/test/parser/temple_namespace_void_test.exs
+++ b/test/parser/temple_namespace_void_test.exs
@@ -3,7 +3,6 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do
alias Temple.Parser.TempleNamespaceVoid
alias Temple.Parser.VoidElementsAliases
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is a Temple aliased nonvoid element" do
@@ -50,18 +49,4 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- Temple.meta(content: "foo")
- end
- |> TempleNamespaceVoid.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result == ~s|\n|
- end
- end
end
diff --git a/test/parser/text_test.exs b/test/parser/text_test.exs
index 432df1c..39f207a 100644
--- a/test/parser/text_test.exs
+++ b/test/parser/text_test.exs
@@ -2,7 +2,6 @@ defmodule Temple.Parser.TextTest do
use ExUnit.Case, async: true
alias Temple.Parser.Text
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is a string literal" do
@@ -29,16 +28,4 @@ defmodule Temple.Parser.TextTest do
assert %Text{text: text} == ast
end
end
-
- describe "Temple.Generator.to_eex/1" do
- test "emits eex" do
- result =
- "string literal"
- |> Text.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result == ~s|string literal\n|
- end
- end
end
diff --git a/test/parser/utils_test.exs b/test/parser/utils_test.exs
index 7030e9b..1d9c93e 100644
--- a/test/parser/utils_test.exs
+++ b/test/parser/utils_test.exs
@@ -3,30 +3,63 @@ defmodule Temple.Parser.UtilsTest do
alias Temple.Parser.Utils
- describe "runtime_attrs/1" do
- test "compiles keyword lists and maps into html attributes" do
- attrs_map = %{
- class: "text-red",
- id: "form1",
- disabled: false,
- inner_block: %{}
- }
+ describe "compile_attrs/1" do
+ test "returns a list of text nodes for static attributes" do
+ attrs = [class: "text-red", id: "error", phx_submit: :save, data_number: 99]
- attrs_kw = [
- class: "text-red",
- id: "form1",
- disabled: true,
- inner_block: %{}
- ]
+ actual = Utils.compile_attrs(attrs)
- assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_map)
- assert {:safe, ~s| class="text-red" id="form1" disabled|} == Utils.runtime_attrs(attrs_kw)
+ assert [
+ {:text, ~s' class="text-red"'},
+ {:text, ~s' id="error"'},
+ {:text, ~s' phx-submit="save"'},
+ {:text, ~s' data-number="99"'}
+ ] == actual
end
- test "class accepts a keyword list which conditionally emits classes" do
- attrs = [class: ["text-red": false, "text-blue": true], id: "form1"]
+ test "returns a list of text and expr nodes for attributes with runtime values" do
+ class_ast = quote(do: @class)
+ id_ast = quote(do: @id)
+ attrs = [class: class_ast, id: id_ast, disabled: false, checked: true]
- assert {:safe, ~s| class="text-blue" id="form1"|} == Utils.runtime_attrs(attrs)
+ actual = Utils.compile_attrs(attrs)
+
+ assert [
+ {:text, ~s' class="'},
+ {:expr, class_ast},
+ {:text, ~s'"'},
+ {:text, ~s' id="'},
+ {:expr, id_ast},
+ {:text, ~s'"'},
+ {:text, ~s' checked'}
+ ] == actual
+ end
+
+ test "returns a list of text and expr nodes for the class object syntax" do
+ class_ast = quote(do: @class)
+
+ list =
+ quote do
+ ["text-red": unquote(class_ast)]
+ end
+
+ expr =
+ quote do
+ String.trim_leading(for {class, true} <- unquote(list), into: "", do: " #{class}")
+ end
+
+ attrs = [class: ["text-red": class_ast]]
+
+ actual = Utils.compile_attrs(attrs)
+
+ assert [
+ {:text, ~s' class="'},
+ {:expr, result_expr},
+ {:text, ~s'"'}
+ ] = actual
+
+ # the ast metadata is different, let's just compare stringified versions
+ assert Macro.to_string(result_expr) == Macro.to_string(expr)
end
end
end
diff --git a/test/parser/void_elements_aliases_test.exs b/test/parser/void_elements_aliases_test.exs
index a2a898a..68c4747 100644
--- a/test/parser/void_elements_aliases_test.exs
+++ b/test/parser/void_elements_aliases_test.exs
@@ -2,7 +2,6 @@ defmodule Temple.Parser.VoidElementsAliasesTest do
use ExUnit.Case, async: true
alias Temple.Parser.VoidElementsAliases
- alias Temple.Support.Utils
describe "applicable?/1" do
test "returns true when the node is a nonvoid element or alias" do
@@ -55,18 +54,4 @@ defmodule Temple.Parser.VoidElementsAliasesTest do
} = ast
end
end
-
- describe "to_eex/1" do
- test "emits eex" do
- result =
- quote do
- meta content: "foo"
- end
- |> VoidElementsAliases.run()
- |> Temple.Generator.to_eex()
- |> Utils.iolist_to_binary()
-
- assert result == ~s|\n|
- end
- end
end
diff --git a/test/support/components/component.ex b/test/support/components/component.ex
deleted file mode 100644
index 6a8666d..0000000
--- a/test/support/components/component.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.Component do
- import Temple.Component
-
- render do
- div do
- slot :default
- end
- end
-end
diff --git a/test/support/components/component2.ex b/test/support/components/component2.ex
deleted file mode 100644
index d719937..0000000
--- a/test/support/components/component2.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.Component2 do
- import Temple.Component
-
- render do
- div class: @class do
- slot :default
- end
- end
-end
diff --git a/test/support/components/has_temple.ex b/test/support/components/has_temple.ex
deleted file mode 100644
index a404017..0000000
--- a/test/support/components/has_temple.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.HasTemple do
- import Temple.Component
-
- render do
- div class: @temple[:class] do
- slot :default
- end
- end
-end
diff --git a/test/support/components/inner.ex b/test/support/components/inner.ex
deleted file mode 100644
index 841e9fb..0000000
--- a/test/support/components/inner.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.Inner do
- import Temple.Component
-
- render do
- div id: "inner", outer_id: @outer_id do
- slot :default
- end
- end
-end
diff --git a/test/support/components/outer.ex b/test/support/components/outer.ex
deleted file mode 100644
index 0b1691f..0000000
--- a/test/support/components/outer.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.Outer do
- import Temple.Component
-
- render do
- c Temple.Components.Inner, outer_id: "from-outer" do
- slot :default, %{}
- end
- end
-end
diff --git a/test/support/components/section.ex b/test/support/components/section.ex
deleted file mode 100644
index 3772901..0000000
--- a/test/support/components/section.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.Section do
- import Temple.Component
-
- render do
- section class: "foo!" do
- slot :default
- end
- end
-end
diff --git a/test/support/components/void_component.ex b/test/support/components/void_component.ex
deleted file mode 100644
index 3338caa..0000000
--- a/test/support/components/void_component.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule Temple.Components.VoidComponent do
- import Temple.Component
-
- render do
- div class: "void!!" do
- "bar"
- end
- end
-end
diff --git a/test/support/components/with_funcs.ex b/test/support/components/with_funcs.ex
deleted file mode 100644
index c2a3052..0000000
--- a/test/support/components/with_funcs.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Temple.Components.WithFuncs do
- import Temple.Component
-
- def get_class(:bar) do
- "barbarbar"
- end
-
- def get_class(_) do
- "foofoofoo"
- end
-
- render do
- div class: get_class(@foo) do
- slot :default
- end
- end
-end
diff --git a/test/support/components/with_slot.ex b/test/support/components/with_slot.ex
deleted file mode 100644
index f1fc64e..0000000
--- a/test/support/components/with_slot.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule Temple.Components.WithSlot do
- import Temple.Component
-
- render do
- div do
- slot :header, value: "Header"
-
- div class: "wrapped" do
- slot :default
- end
- end
- end
-end
diff --git a/test/support/utils.ex b/test/support/utils.ex
deleted file mode 100644
index e47adf0..0000000
--- a/test/support/utils.ex
+++ /dev/null
@@ -1,52 +0,0 @@
-defmodule Temple.Support.Utils do
- defmacro __using__(_) do
- quote do
- import Kernel, except: [==: 2, =~: 2]
- import unquote(__MODULE__)
- end
- end
-
- def a == b when is_binary(a) and is_binary(b) do
- a = String.replace(a, "\n", "")
- b = String.replace(b, "\n", "")
-
- Kernel.==(a, b)
- end
-
- def a =~ b when is_binary(a) and is_binary(b) do
- a = String.replace(a, "\n", "")
- b = String.replace(b, "\n", "")
-
- Kernel.=~(a, b)
- end
-
- def env do
- require Temple.Component
-
- __ENV__
- end
-
- def evaluate_template(template, assigns \\ %{}) do
- template
- |> EEx.compile_string(engine: Phoenix.HTML.Engine)
- |> Code.eval_quoted([assigns: assigns], env())
- |> elem(0)
- |> Phoenix.HTML.safe_to_string()
- end
-
- @doc """
- Converts an iolist to a binary and appends a new line.
- """
- def iolist_to_binary(iolist) do
- iolist
- |> :erlang.iolist_to_binary()
- |> append_new_line()
- end
-
- @doc """
- Appends a new line to a string.
- """
- def append_new_line(string) do
- string <> "\n"
- end
-end
diff --git a/test/temple/renderer_test.exs b/test/temple/renderer_test.exs
new file mode 100644
index 0000000..86dfb97
--- /dev/null
+++ b/test/temple/renderer_test.exs
@@ -0,0 +1,505 @@
+defmodule Temple.RendererTest do
+ use ExUnit.Case, async: true
+
+ import Temple
+
+ require Temple.Renderer
+ alias Temple.Renderer
+
+ describe "compile/1" do
+ test "produces renders a text node" do
+ result =
+ Renderer.compile do
+ "hello world"
+ end
+
+ assert "hello world\n" == result
+ end
+
+ test "produces renders a div" do
+ result =
+ Renderer.compile do
+ div class: "hello world" do
+ "hello world"
+
+ span id: "name", do: "bob"
+ end
+ end
+
+ # html
+ expected = """
+
+ hello world
+ bob
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "produces renders a void elements" do
+ result =
+ Renderer.compile do
+ div class: "hello world" do
+ "hello world"
+
+ input type: "button", value: "Submit"
+ input type: "button", value: "Submit"
+ end
+ end
+
+ # html
+ expected = """
+
+ hello world
+
+
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "a match does not emit" do
+ result =
+ Renderer.compile do
+ div class: "hello world" do
+ _ = "hello world"
+
+ span id: "name", do: "bob"
+ end
+ end
+
+ # html
+ expected = """
+
+ bob
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "handles simple expression inside attributes" do
+ assigns = %{statement: "hello world", color: "green"}
+
+ result =
+ Renderer.compile do
+ div class: @color do
+ @statement
+ end
+ end
+
+ # html
+ expected = """
+
+ hello world
+
+
+ """
+
+ assert expected == result
+ end
+
+ # test "handles simple expression are the entire attributes" do
+ # assigns = %{statement: "hello world", attributes: [class: "green"]}
+
+ # result =
+ # Renderer.compile do
+ # div @attributes do
+ # @statement
+ # end
+ # end
+
+ # # html
+ # expected = """
+ #
+ # hello world
+ #
+
+ # """
+
+ # assert expected == result
+ # end
+
+ test "handles simple expression with @ assign" do
+ assigns = %{statement: "hello world"}
+
+ result =
+ Renderer.compile do
+ div do
+ @statement
+ end
+ end
+
+ # html
+ expected = """
+
+ hello world
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "handles multi line expression" do
+ assigns = %{names: ["alice", "bob", "carol"]}
+
+ result =
+ Renderer.compile do
+ div do
+ for name <- @names do
+ span class: "name", do: name
+ end
+ end
+ end
+
+ # html
+ expected = """
+
+ alice
+ bob
+ carol
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "if expression" do
+ for val <- [true, false] do
+ assigns = %{value: val}
+
+ result =
+ Renderer.compile do
+ div do
+ if @value do
+ span do: "true"
+ else
+ span do: "false"
+ end
+ end
+ end
+
+ # html
+ expected = """
+
+ #{val}
+
+
+
+ """
+
+ assert expected == result
+ end
+ end
+
+ test "with expression" do
+ for val <- [true, false, "bobby"] do
+ assigns = %{value: val}
+
+ result =
+ Renderer.compile do
+ div do
+ with false <- @value,
+ true <- "motch" not in ["lame", "not funny"] do
+ span do: "false"
+ else
+ true ->
+ span do: true
+
+ _ ->
+ span do: "bobby"
+ end
+ end
+ end
+
+ # html
+ expected = """
+
+ #{val}
+
+
+
+ """
+
+ assert expected == result
+ end
+ end
+
+ test "handles case expression" do
+ assigns = %{name: "alice"}
+
+ # html
+ expected = """
+
+ alice is the best
+
+
+
+ """
+
+ result =
+ Renderer.compile do
+ div do
+ case @name do
+ "bob" ->
+ span do: "bob is cool"
+
+ "alice" ->
+ span id: "correct answer", do: "alice is the best"
+
+ _ ->
+ span do: "everyone is lame"
+ end
+ end
+ end
+
+ assert expected == result
+ end
+
+ test "handles anonymous functions" do
+ assigns = %{names: ["alice", "bob", "carol"]}
+
+ result =
+ Renderer.compile do
+ div do
+ Enum.map(@names, fn name ->
+ span class: "name", do: name
+ end)
+ end
+ end
+
+ # html
+ expected = """
+
+ alice
+
+ bob
+
+ carol
+
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ def super_map(enumerable, func, _extra_args) do
+ Enum.map(enumerable, func)
+ end
+
+ test "handles anonymous functions with subsequent args" do
+ assigns = %{names: ["alice", "bob", "carol"]}
+
+ result =
+ Renderer.compile do
+ div do
+ super_map(
+ @names,
+ fn name ->
+ span class: "name", do: name
+ end,
+ "hello world"
+ )
+ end
+ end
+
+ # html
+ expected = """
+
+ alice
+
+ bob
+
+ carol
+
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ def basic_component(_assigns) do
+ temple do
+ div do
+ "I am a basic component"
+ end
+ end
+ end
+
+ test "basic component" do
+ result =
+ Renderer.compile do
+ div do
+ c &basic_component/1
+ end
+ end
+
+ # html
+ expected = """
+
+
+ I am a basic component
+
+
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ def default_slot(assigns) do
+ temple do
+ div do
+ "I am above the slot"
+ slot :default
+ end
+ end
+ end
+
+ test "component with default slot" do
+ result =
+ Renderer.compile do
+ div do
+ c &default_slot/1 do
+ span do: "i'm a slot"
+ end
+ end
+ end
+
+ # html
+ expected = """
+
+
+ I am above the slot
+ i'm a slot
+
+
+
+
+
+
+ """
+
+ assert expected == result
+ end
+
+ def named_slot(assigns) do
+ temple do
+ div do
+ "#{@name} is above the slot"
+ slot :default
+ end
+
+ footer do
+ slot :footer, %{name: @name}
+ end
+ end
+ end
+
+ test "component with a named slot" do
+ result =
+ Renderer.compile do
+ div do
+ c &named_slot/1, name: "motchy boi" do
+ span do: "i'm a slot"
+
+ slot :footer, %{name: name} do
+ p do
+ "#{name}'s in the footer!"
+ end
+ end
+ end
+ end
+ end
+
+ # heex
+ expected = """
+
+
+ motchy boi is above the slot
+ i'm a slot
+
+
+
+
+
+
+
+
+ """
+
+ assert expected == result
+ end
+ end
+
+ describe "special attribute stuff" do
+ test "class object syntax" do
+ result =
+ Renderer.compile do
+ div class: ["hello world": false, "text-red": true] do
+ "hello world"
+ end
+ end
+
+ # html
+ expected = """
+
+ hello world
+
+
+ """
+
+ assert expected == result
+ end
+
+ test "boolean attributes only emit correctly with truthy values" do
+ result =
+ Renderer.compile do
+ input type: "text", disabled: true, placeholder: "Enter some text..."
+ end
+
+ # html
+ expected = """
+
+ """
+
+ assert expected == result
+ end
+
+ test "boolean attributes don't emit with falsy values" do
+ result =
+ Renderer.compile do
+ input type: "text", disabled: false, placeholder: "Enter some text..."
+ end
+
+ # html
+ expected = """
+
+ """
+
+ assert expected == result
+ end
+ end
+end
diff --git a/test/temple_test.exs b/test/temple_test.exs
index fecbd14..132994c 100644
--- a/test/temple_test.exs
+++ b/test/temple_test.exs
@@ -1,512 +1,32 @@
defmodule TempleTest do
use ExUnit.Case, async: true
- use Temple
- use Temple.Support.Utils
+ import Temple
- test "renders an attribute on a div passed as a variable" do
- result =
- temple do
- div class: "hello" do
- div class: "hi"
- end
- end
+ describe "temple/1" do
+ test "works" do
+ assigns = %{name: "mitch"}
- assert result == ~s"""
-
-
-
-
- """
- end
-
- test "renders void element" do
- result =
- temple do
- input name: "password"
- end
-
- assert result == ~s{}
- end
-
- test "renders a text node from the text keyword with siblings" do
- result =
- temple do
- div class: "hello" do
- "hi"
- "foo"
- end
- end
-
- assert result == ~s"""
-
- hi
- foo
-
- """
- end
-
- test "renders a variable text node as eex" do
- result =
- temple do
- div class: "hello" do
- foo
- end
- end
-
- assert result == ~s"""
-
- <%= foo %>
-
- """
- end
-
- test "renders an assign text node as eex" do
- result =
- temple do
- div class: "hello" do
- @foo
- end
- end
-
- assert result == ~s"""
-
- <%= @foo %>
-
- """
- end
-
- test "renders a match expression" do
- result =
- temple do
- x = 420
-
- div do
- "blaze it"
- end
- end
-
- assert result == ~s"""
- <% x = 420 %>
-
- blaze it
-
- """
- end
-
- test "renders a non-match expression" do
- result =
- temple do
- IO.inspect(:foo)
-
- div do
- "bar"
- end
- end
-
- assert result == ~s"""
- <%= IO.inspect(:foo) %>
-
- bar
-
- """
- end
-
- test "renders an expression in attr as eex" do
- result =
- temple do
- div class: foo <> " bar"
- end
-
- assert result ==
- ~s|
" bar")} %>>
|
- end
-
- test "renders an attribute on a div passed as a variable as eex" do
- result =
- temple do
- div class: Enum.map([:one, :two], fn x -> x end) do
- div class: "hi"
- end
- end
-
- assert result ==
- ~s"""
-
x end))} %>>
-
-
-
- """
- end
-
- test "renders a for comprehension as eex" do
- result =
- temple do
- for x <- 1..5 do
- div class: "hi"
- end
- end
-
- assert result == ~s"""
- <%= for(x <- 1..5) do %>
-
-
- <% end %>
- """
- end
-
- test "renders an if expression as eex" do
- result =
- temple do
- if true == false do
- div class: "hi"
- end
- end
-
- assert result == ~s"""
- <%= if(true == false) do %>
-
-
- <% end %>
- """
- end
-
- test "renders an if/else expression as eex" do
- result =
- temple do
- if true == false do
- div class: "hi"
- else
- div class: "haha"
- end
- end
-
- assert result ==
- ~s"""
- <%= if(true == false) do %>
-
-
- <% else %>
-
-
- <% end %>
- """
- end
-
- test "renders an unless expression as eex" do
- result =
- temple do
- unless true == false do
- div class: "hi"
- end
- end
-
- assert result == ~s"""
- <%= unless(true == false) do %>
-
-
- <% end %>
- """
- end
-
- test "renders a case expression as eex" do
- result =
- temple do
- case @foo do
- :baz ->
- some_component(form: @form)
- end
- end
-
- expected = ~S"""
- <%= case(@foo) do %>
- <% :baz -> %>
- <%= some_component(form: @form) %>
- <% end %>
- """
-
- assert result == expected
- end
-
- test "renders multiline anonymous function with 1 arg before the function" do
- result =
- temple do
- form_for(Routes.user_path(@conn, :create), fn f ->
- "Name: "
- text_input(f, :name)
- end)
- end
-
- assert result ==
- ~s"""
- <%= form_for Routes.user_path(@conn, :create), fn f -> %>
- Name:
- <%= text_input(f, :name) %>
- <% end %>
- """
- end
-
- test "renders multiline anonymous functions with 2 args before the function" do
- result =
- temple do
- form_for(@changeset, Routes.user_path(@conn, :create), fn f ->
- "Name: "
- text_input(f, :name)
- end)
- end
-
- assert result ==
- ~s"""
- <%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
- Name:
- <%= text_input(f, :name) %>
- <% end %>
- """
- end
-
- test "renders multiline anonymous functions with complex nested children" do
- result =
- temple do
- form_for(@changeset, Routes.user_path(@conn, :create), fn f ->
- div do
- "Name: "
- text_input(f, :name)
- end
- end)
- end
-
- assert result ==
- ~s"""
- <%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
-
- Name:
- <%= text_input(f, :name) %>
-
- <% end %>
- """
- end
-
- test "renders multiline anonymous function with 3 arg before the function" do
- result =
- temple do
- form_for(@changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f ->
- "Name: "
- text_input(f, :name)
- end)
- end
-
- assert result ==
- ~s"""
- <%= form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> %>
- Name:
- <%= text_input(f, :name) %>
- <% end %>
- """
- end
-
- test "renders multiline anonymous function with 1 arg before the function and 1 arg after" do
- result =
- temple do
- form_for(
- @changeset,
- fn f ->
- "Name: "
- text_input(f, :name)
- end,
- foo: :bar
- )
- end
-
- assert result ==
- ~s"""
- <%= form_for @changeset, fn f -> %>
- Name:
- <%= text_input(f, :name) %>
- <% end, [foo: :bar] %>
- """
- end
-
- test "tags prefixed with Temple. should be interpreted as temple tags" do
- result =
- temple do
- div do
- Temple.span do
- "bob"
+ result =
+ temple do
+ div class: "hello" do
+ div class: "hi" do
+ @name
+ end
end
end
- end
- assert result == ~s"""
-
-
- bob
-
-
- """
- end
+ # heex
+ expected = """
+
+
+ mitch
+
- test "can pass do as an arg instead of a block" do
- result =
- temple do
- div class: "font-bold" do
- "Hello, world"
- end
+
- div class: "font-bold", do: "Hello, world"
- div do: "Hello, world"
- end
+ """
- assert result ==
- ~s"""
-
- Hello, world
-
-
- Hello, world
-
-
- Hello, world
-
- """
- end
-
- test "for with 2 generators" do
- result =
- temple do
- for x <- 1..5, y <- 6..10 do
- div do: x
- div do: y
- end
- end
-
- assert result ==
- ~s"""
- <%= for(x <- 1..5, y <- 6..10) do %>
-
- <%= x %>
-
-
- <%= y %>
-
- <% end %>
- """
- end
-
- test "can pass an expression as assigns" do
- result =
- temple do
- fieldset if true == false, do: [disabled: true], else: [] do
- input type: "text"
- end
- end
-
- assert result ==
- ~s"""
-
- """
- end
-
- test "can pass a variable as assigns" do
- result =
- temple do
- fieldset foo_bar do
- input type: "text"
- end
- end
-
- assert result ==
- ~s"""
-
- """
- end
-
- test "can pass a function as assigns" do
- result =
- temple do
- fieldset Foo.foo_bar() do
- input type: "text"
- end
- end
-
- assert result ==
- ~s"""
-
- """
- end
-
- test "hr tag works" do
- assigns = %{foo: [class: "foofoo"]}
-
- result =
- temple do
- div do: "foo"
- hr()
- div do: "foo"
- hr @foo
- div do: "bar"
- hr class: "foofoo"
- div do: "bar"
- end
-
- assert evaluate_template(result, assigns) ==
- ~s"""
-
- foo
-
-
-
- foo
-
-
-
- bar
-
-
-
- bar
-
- """
- end
-
- test "boolean attributes" do
- assigns = %{is_true: true, is_false: false}
-
- result =
- temple do
- input type: "text", disabled: true
- input type: "text", disabled: false
-
- input type: "text", disabled: @is_true
- input type: "text", disabled: @is_false
- end
-
- assert evaluate_template(result, assigns) ==
- ~s{\n\n\n}
- end
-
- test "class attribute can be pass a keyword list of class/boolean pairs" do
- assigns = %{is_true: true, is_false: false}
-
- result =
- temple do
- div class: ["text-blue": @is_false, "text-red": true] do
- "foo"
- end
- end
-
- assert evaluate_template(result, assigns) == ~s"""
-
- foo
-
- """
+ assert expected == result
+ end
end
end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 869559e..46f47bb 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1 +1,6 @@
-ExUnit.start()
+Code.put_compiler_option(
+ :parser_options,
+ Keyword.put(Code.get_compiler_option(:parser_options), :token_metadata, true)
+)
+
+ExUnit.start(exclude: [skip: true])
diff --git a/test/whitespace_test.exs b/test/whitespace_test.exs
deleted file mode 100644
index c663026..0000000
--- a/test/whitespace_test.exs
+++ /dev/null
@@ -1,46 +0,0 @@
-defmodule Temple.WhitespaceTest do
- use ExUnit.Case, async: true
-
- import Temple
-
- alias Temple.Support.Utils
-
- test "only emits a single new line" do
- result =
- temple do
- div class: "hello" do
- span id: "foo" do
- "Howdy, "
- end
-
- div class: "hi" do
- "Jim Bob"
- end
-
- c WhoaNelly, foo: "bar" do
- slot :silver do
- "esketit"
- end
- end
- end
- end
- |> Utils.append_new_line()
-
- expected = ~s"""
-