MishkaChelekom.CmsBundle.Heex (Mishka Chelekom v0.0.9-alpha.20)

Copy Markdown View Source

HEEx parsing for the v3 bundle exporter. One public surface for three jobs:

  • tokenize/2 — return a unified token stream for raw .heex source by composing EEx.tokenize/1 with Phoenix.LiveView.Tokenizer.tokenize/5. This is the same recipe Phoenix.LiveView.HTMLFormatter (the official mix format plugin for HEEx) uses internally — neither tokenizer alone handles raw HEEx (EEx.tokenize leaves HTML opaque inside :text tokens; Phoenix.LiveView.Tokenizer raises on <%= … %> mid-content).

  • extract/2 — find every top-level chelekom-component invocation in a HEEx demo file and return its verbatim source plus line. Used by mix mishka.ui.export --cms to populate extra.demo_examples per component.

  • rewrite/3 — rewrite sibling <.X> references to <.component component_name="<kit>-<X-slug>" site={…} …/>. Used by the bundle exporter to make cross-component calls go through the runtime helper.

Source slicing (line/column → byte range) is private to this module — only extract/2 and rewrite/3 need it.

Token shapes

{:tag, name, attrs, meta}               `<div>`, `<input/>`
{:local_component, name, attrs, meta}   `<.button>`, `<.icon/>`
{:remote_component, "Mod.X", attrs, meta}
{:slot, name, attrs, meta}              `<:inner_block>`
{:close, kind, name, meta}              matching close tag
{:text, raw, meta}                      text between tags
{:eex, type, expr, meta}                `<%=  %>` etc.
{:eex_comment, body, meta}              `<%!--  --%>`

Failure mode

All three public fns return something safe on parse failure:

Strict callers (e.g. mix mishka.ui.verify) can compare against tokenize/2 directly to surface errors at author time.

Summary

Functions

Walk text, extract every top-level invocation whose tag name is in kit_components. Returns extractions in source order. Returns [] on parse failure.

Rewrite a template body. Every <.NAME …> whose NAME is in siblings becomes <.component component_name="<kit>-<slug>" site={assigns[:site]} …>; matching </.NAME> becomes </.component>. Other tags, EEx blocks, and string literals are preserved verbatim.

Tokenize raw HEEx source. Returns {:ok, tokens} in source order or {:error, message} on parse failure.

Types

extraction()

@type extraction() :: %{
  component: String.t(),
  source: String.t(),
  line: pos_integer()
}

sibling_set()

@type sibling_set() :: MapSet.t(String.t())

token()

@type token() :: tuple()

Functions

extract(text, kit_components)

@spec extract(String.t() | nil, sibling_set()) :: [extraction()]

Walk text, extract every top-level invocation whose tag name is in kit_components. Returns extractions in source order. Returns [] on parse failure.

rewrite(template, siblings, kit_name)

@spec rewrite(String.t() | nil, sibling_set(), String.t()) :: String.t() | nil

Rewrite a template body. Every <.NAME …> whose NAME is in siblings becomes <.component component_name="<kit>-<slug>" site={assigns[:site]} …>; matching </.NAME> becomes </.component>. Other tags, EEx blocks, and string literals are preserved verbatim.

Returns the input unchanged on parse failure.

tokenize(source, opts \\ [])

@spec tokenize(
  String.t(),
  keyword()
) :: {:ok, [token()]} | {:error, String.t()}

Tokenize raw HEEx source. Returns {:ok, tokens} in source order or {:error, message} on parse failure.