Mob.Sigil (mob v0.7.19)

Copy Markdown View Source

The ~MOB sigil for declarative native UI.

Compiles a tag template to a Mob.Renderer-compatible node map at compile time using NimbleParsec. Expressions in {...} are evaluated in the caller's scope at runtime.

Use ~MOB(...) for single nodes or ~MOB"""...""" for nested layouts.

Examples

import Mob.Sigil

# Self-closing
~MOB(<Text text="Hello" />)
#=> %{type: :text, props: %{text: "Hello"}, children: []}

# Nested layout
~MOB"""
<Column padding={:space_md}>
  <Text text="Title" text_size={:xl} />
  <Button text="OK" on_tap={{self(), :ok}} />
</Column>
"""

# Expression child — inject any node map or list of maps
~MOB"""
<Column>
  {Enum.map(items, fn i -> ~MOB(<Text text={i} />) end)}
</Column>
"""

Assigns shorthand

Inside a {...} expression, @foo rewrites to assigns.foo (matching Phoenix HEEx), so a render(assigns) body reads cleanly:

~MOB(<Text text={@title} />)   # same as text={assigns.title}

@foo only works where a variable named assigns is in scope — i.e. a screen's or component's render(assigns). Reusable helper functions take positional arguments instead, so use the argument directly there:

# Screen render — assigns is in scope, @ works:
def render(assigns), do: ~MOB(<Text text={@title} />)

# Helper — no assigns; interpolate the argument, don't use @:
def label(title), do: ~MOB(<Text text={title} />)

Using @foo where no assigns exists raises a CompileError naming the fix, rather than a cryptic "undefined variable assigns".

Control attributes — :if and :for

Two LiveView-style directives wrap an element without extra ceremony. Both take a {expr} value and may read @assigns.

# Conditional — omitted entirely when the expression is falsy
~MOB(<Badge text="New" :if={@unread > 0} />)

# Comprehension — one element per item; splices into the parent
~MOB"""
<Column>
  <Row :for={user <- @users}>
    <Text text={user.name} />
  </Row>
</Column>
"""

Combine them — :if then acts as a comprehension filter (an element is produced only for items where the condition holds):

<Text text={n} :for={n <- @nums} :if={rem(n, 2) == 0} />

Tag whitelist

Tags are validated against priv/tags/ios.txt and priv/tags/android.txt at compile time. Unknown tags emit a warning but still pass through — the type atom is derived by converting PascalCase to snake_case (e.g. TabBar:tab_bar). This allows new native tags to be used before the whitelist is updated.

Summary

Functions

Parses the given binary as brace_content.

Parses the given binary as node.

Parses the given binary as parse_template.

Compiles a ~MOB(...) or ~MOB"""...""" template into a native UI node map. Parsed at compile time; {expr} values evaluated at runtime in the caller's scope.

Normalizes a child's value to a list of UI-node maps for the surrounding sigil. Single nodes wrap into a one-element list; lists pass through; nil (a :if that didn't render) drops to []. Public so the sigil-generated AST can call it by FQ name; not part of the application API.

Functions

brace_content(binary, opts \\ [])

@spec brace_content(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()

Parses the given binary as brace_content.

Returns {:ok, [token], rest, context, position, byte_offset} or {:error, reason, rest, context, line, byte_offset} where position describes the location of the brace_content (start position) as {line, offset_to_start_of_line}.

To column where the error occurred can be inferred from byte_offset - offset_to_start_of_line.

Options

  • :byte_offset - the byte offset for the whole binary, defaults to 0
  • :line - the line and the byte offset into that line, defaults to {1, byte_offset}
  • :context - the initial context value. It will be converted to a map

node(binary, opts \\ [])

@spec node(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()

Parses the given binary as node.

Returns {:ok, [token], rest, context, position, byte_offset} or {:error, reason, rest, context, line, byte_offset} where position describes the location of the node (start position) as {line, offset_to_start_of_line}.

To column where the error occurred can be inferred from byte_offset - offset_to_start_of_line.

Options

  • :byte_offset - the byte offset for the whole binary, defaults to 0
  • :line - the line and the byte offset into that line, defaults to {1, byte_offset}
  • :context - the initial context value. It will be converted to a map

parse_template(binary, opts \\ [])

@spec parse_template(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()

Parses the given binary as parse_template.

Returns {:ok, [token], rest, context, position, byte_offset} or {:error, reason, rest, context, line, byte_offset} where position describes the location of the parse_template (start position) as {line, offset_to_start_of_line}.

To column where the error occurred can be inferred from byte_offset - offset_to_start_of_line.

Options

  • :byte_offset - the byte offset for the whole binary, defaults to 0
  • :line - the line and the byte offset into that line, defaults to {1, byte_offset}
  • :context - the initial context value. It will be converted to a map

sigil_MOB(arg, modifiers)

(macro)

Compiles a ~MOB(...) or ~MOB"""...""" template into a native UI node map. Parsed at compile time; {expr} values evaluated at runtime in the caller's scope.

wrap_child(list)

@spec wrap_child(list() | map() | nil) :: list()

Normalizes a child's value to a list of UI-node maps for the surrounding sigil. Single nodes wrap into a one-element list; lists pass through; nil (a :if that didn't render) drops to []. Public so the sigil-generated AST can call it by FQ name; not part of the application API.