MishkaCmsCore.Builder.Literal (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Quoted Elixir back into the term it stands for, when it stands for a term and nothing else.

Why not Code.eval_quoted/1

Because every caller here is holding something an author, or a package, or a database row supplied. Evaluating it would run it. This walks it instead: a map, a tuple, a list and the three scalar kinds resolve, and everything else — a call, a variable, an assign, an interpolation — answers :error and is left as the source text it always was.

The two directions

term/1 is the exact inverse of Macro.escape/1, which is what makes data?/1 a one-liner rather than a second allowlist kept in step with the first by hand. A value is data when escaping it and unescaping it gives it back — and that property is the same one MishkaCmsCore.Builder.Command relies on to print a term as source without an Inspect of its own.

Summary

Functions

Whether a term is data a page can carry — no function, no pid, no reference.

The term a quoted expression stands for, or :error.

The term a source string stands for, or :error.

Functions

data?(value)

@spec data?(term()) :: boolean()

Whether a term is data a page can carry — no function, no pid, no reference.

The check a writer needs before printing a term as source, and the trap it is guarding against is quiet: a three-element tuple like {:foo, [], nil} is a perfectly ordinary term AND a valid quoted variable, so printing it unescaped writes foo into somebody's page. Escaping first is what makes it print as a tuple; this says whether escaping it was enough.

iex> MishkaCmsCore.Builder.Literal.data?(%{"a" => [1, {:b, "c"}]})
true

iex> MishkaCmsCore.Builder.Literal.data?(&String.trim/1)
false

term(items)

@spec term(Macro.t()) :: {:ok, term()} | :error

The term a quoted expression stands for, or :error.

Quoted Elixir says what a thing is by its shape: a map is {:%{}, meta, pairs}, a tuple of any size but two is {:{}, meta, items}, a two-tuple is itself — and every OTHER three-element tuple is a call, a variable or an alias, which is precisely what must not resolve. Listing what is allowed rather than what is not is the only way round that stays right when a syntax is added.

value(source)

@spec value(String.t()) :: {:ok, term()} | :error

The term a source string stands for, or :error.

iex> MishkaCmsCore.Builder.Literal.value(~s|%{"kind" => :info}|)
{:ok, %{"kind" => :info}}

iex> MishkaCmsCore.Builder.Literal.value("@assign")
:error