diff --git a/CHANGELOG.md b/CHANGELOG.md --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ following the [publication and resolution spec](https://atproto.com/specs/lexicon#lexicon-publication-and-resolution). - `mix atex.lexicons.resolve` task for resolving one or more lexicons by NSID and writing to a JSON file. +- Sigils for `Atex.AtURI` and `Atex.TID`, `~AT"at://..."` and `~TID"..."` + respectively. ## [0.8.0] - 2026-03-29 diff --git a/lib/atex/aturi.ex b/lib/atex/aturi.ex --- a/lib/atex/aturi.ex +++ b/lib/atex/aturi.ex @@ -140,6 +140,36 @@ "at://#{uri.authority}/#{uri.collection}/#{uri.rkey}" |> String.trim_trailing("/") end + @doc """ + Sigil for constructing an `Atex.AtURI` struct from a string literal. + Raises `ArgumentError` if the string is not a valid `at://` URI. + + ## Examples + + iex> import Atex.AtURI + iex> ~AT"at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26" + ~AT"at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26" + + """ + defmacro sigil_AT({:<<>>, _meta, [value]}, _modifiers) when is_binary(value) do + case Atex.AtURI.new(value) do + {:ok, uri} -> + Macro.escape(uri) + + :error -> + raise ArgumentError, "invalid at:// URI: #{inspect(value)}" + end + end + + defmacro sigil_AT({:<<>>, _meta, _parts} = ast, _modifiers) do + quote do + case Atex.AtURI.new(unquote(ast)) do + {:ok, uri} -> uri + :error -> raise ArgumentError, "invalid at:// URI: #{inspect(unquote(ast))}" + end + end + end + defp from_named_captures(%{"authority" => authority, "collection" => "", "rkey" => ""}), do: %__MODULE__{authority: authority} @@ -157,3 +187,7 @@ defimpl String.Chars, for: Atex.AtURI do def to_string(uri), do: Atex.AtURI.to_string(uri) end + +defimpl Inspect, for: Atex.AtURI do + def inspect(uri, _opts), do: ~s(~AT"#{Atex.AtURI.to_string(uri)}") +end diff --git a/lib/atex/tid.ex b/lib/atex/tid.ex --- a/lib/atex/tid.ex +++ b/lib/atex/tid.ex @@ -182,8 +182,43 @@ false """ @spec match?(String.t()) :: boolean() def match?(value), do: Regex.match?(@re, value) + + @doc """ + Sigil for constructing a `Atex.TID` struct from a string literal at + compile-time or runtime. Raises `ArgumentError` if the string is not a valid + TID. + + ## Examples + + iex> import Atex.TID + iex> ~TID"3jzfcijpj2z2a" + ~TID"3jzfcijpj2z2a" + + """ + defmacro sigil_TID({:<<>>, _meta, [value]}, _modifiers) when is_binary(value) do + case Atex.TID.decode(value) do + {:ok, tid} -> + Macro.escape(tid) + + :error -> + raise ArgumentError, "invalid TID: #{inspect(value)}" + end + end + + defmacro sigil_TID({:<<>>, _meta, _parts} = ast, _modifiers) do + quote do + case Atex.TID.decode(unquote(ast)) do + {:ok, tid} -> tid + :error -> raise ArgumentError, "invalid TID: #{inspect(unquote(ast))}" + end + end + end end defimpl String.Chars, for: Atex.TID do def to_string(tid), do: Atex.TID.encode(tid) end + +defimpl Inspect, for: Atex.TID do + def inspect(tid, _opts), do: ~s(~TID"#{Atex.TID.encode(tid)}") +end