MishkaCmsCore.Runtime.PageBuilder.Markup (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Every Elixir expression a HEEx template contains, found by tokenising it.

Why it exists

Because the questions worth asking about a template — does it read this assign, does it call that helper — are questions about its EXPRESSIONS, and a pattern over the source cannot tell an expression from the text around it. @flash in a comment, in a string, in a <pre> showing somebody a code sample: a pattern finds all three, and a tokeniser finds none of them.

What comes back is source, one string per expression, for Code.string_to_quoted/1 to turn into something walkable — see MishkaCmsCore.Runtime.PageBuilder.CodeReads.

iex> MishkaCmsCore.Runtime.PageBuilder.Markup.expressions(~S|<div id={@id}>{@flash}</div>|)
["@id", "@flash"]

Tokens, not the tree

EEx.tokenize/1 and Phoenix.LiveView.TagEngine.Tokenizer directly, rather than the tree MishkaCmsCore.Runtime.PageBuilder.LiveView.HtmlTokenizer builds on top of them. Nesting is a question this module does not ask, and asking it fails on templates this one can answer for: a fragment like <div class={paint(@tone)}> has no closing tag, so the tree is an error — while the attribute was tokenised perfectly well on the way to finding that out.

What it does with a template it cannot read

Answers [], which every caller has to read as "nothing found" rather than "nothing there". component_arguments/1 is the exception: its caller decides what a component can reach from the answer, so an unreadable template there is :dynamic rather than an empty list.

Summary

Functions

Every expression a template HANDS TO a <.component …/> tag — the values a runtime component can be given, as source.

Every Elixir expression in a template: attribute values, interpolations and EEx tags.

Functions

component_arguments(template)

@spec component_arguments(String.t() | nil) :: {:ok, [String.t()]} | :dynamic

Every expression a template HANDS TO a <.component …/> tag — the values a runtime component can be given, as source.

A literal attribute is not among them. component_name="card" is a string written in the template, so it can carry nothing the template does not already say itself.

:dynamic when what a tag hands over cannot be read off the template at all: a tag that spreads a map built while the page renders, or a template that cannot be tokenised. A caller must read that as "anything could have been handed over", never as "nothing was" — which is the opposite of what expressions/1 promises for the same unreadable template, and the reason the two answers have different shapes.

iex> MishkaCmsCore.Runtime.PageBuilder.Markup.component_arguments(~S|<.component component_name="card" bag={assigns} />|)
{:ok, ["assigns"]}

iex> MishkaCmsCore.Runtime.PageBuilder.Markup.component_arguments(~S|<.component {@attrs} />|)
:dynamic

expressions(template)

@spec expressions(String.t() | nil) :: [String.t()]

Every Elixir expression in a template: attribute values, interpolations and EEx tags.