Add RDF.Namespace.IRI.iri/1 macro

This commit is contained in:
Marcel Otto 2022-06-06 20:49:21 +02:00
parent adf3905ee6
commit 1a51aea606
8 changed files with 123 additions and 5 deletions

View file

@ -14,6 +14,8 @@ The generated namespaces are much more flexible now and compile faster.
- `RDF.Namespace` builders `defnamespace/3` and `create/4` - `RDF.Namespace` builders `defnamespace/3` and `create/4`
- `RDF.Vocabulary.Namespace.create/5` for dynamic creation of `RDF.Vocabulary.Namespace`s - `RDF.Vocabulary.Namespace.create/5` for dynamic creation of `RDF.Vocabulary.Namespace`s
- macro `RDF.Namespace.IRI.iri/1` which allows to resolve `RDF.Namespace` terms
inside of pattern matches
- `RDF.IRI.starts_with?/2` and `RDF.IRI.ends_with?/2` - `RDF.IRI.starts_with?/2` and `RDF.IRI.ends_with?/2`
### Changed ### Changed

54
lib/rdf/namespace/iri.ex Normal file
View file

@ -0,0 +1,54 @@
defmodule RDF.Namespace.IRI do
@moduledoc """
Provides the `iri/1` macro to resolve IRI values inside of pattern matches.
"""
@doc """
A macro which allows to resolve IRI values inside of pattern matches.
Terms of a `RDF.Namespace` (which includes terms of `RDF.Vocabulary.Namespace`)
can't be resolved in pattern matches. This macro allows just that, by wrapping
the terms in a pattern match with a call of this macro.
Note: Only literal values are allowed as arguments of this macro, since the argument
expression needs to be evaluated at compile-time.
## Example
import RDF.Namespace.IRI
case expr do
iri(EX.Foo) -> ...
iri(EX.bar()) -> ...
...
end
"""
defmacro iri({{:., _, [{:__aliases__, _, _} = module_alias, _fun_name]}, _, []} = expr) do
{module, _} = Code.eval_quoted(module_alias, [], __CALLER__)
if RDF.Namespace.namespace?(module) do
resolve_to_iri(expr, __CALLER__)
else
forbidden_iri_expr(expr)
end
end
defmacro iri({:__aliases__, _, _} = expr), do: resolve_to_iri(expr, __CALLER__)
defmacro iri(expr), do: forbidden_iri_expr(expr)
defp resolve_to_iri(expr, env) do
{value, _} = Code.eval_quoted(expr, [], env)
iri = RDF.IRI.new(value)
quote do
unquote(Macro.escape(iri))
end
end
defp forbidden_iri_expr(expr) do
raise ArgumentError, "forbidden expression in RDF.Guard.iri/1 call: #{Macro.to_string(expr)}"
end
end

View file

@ -16,7 +16,8 @@ defmodule RDF.Test.Case do
alias RDF.NS.{RDFS, OWL} alias RDF.NS.{RDFS, OWL}
alias unquote(__MODULE__).{EX, FOAF} alias unquote(__MODULE__).{EX, FOAF}
import RDF, only: [iri: 1, literal: 1, bnode: 1] import RDF, only: [literal: 1, bnode: 1]
import RDF.Namespace.IRI
import unquote(__MODULE__) import unquote(__MODULE__)
import RDF.Sigils import RDF.Sigils

View file

@ -15,7 +15,7 @@ defmodule RDF.DatasetTest do
test "creating an empty dataset with a coercible dataset name" do test "creating an empty dataset with a coercible dataset name" do
assert named_dataset("http://example.com/DatasetName") assert named_dataset("http://example.com/DatasetName")
|> named_dataset?(iri("http://example.com/DatasetName")) |> named_dataset?(~I<http://example.com/DatasetName>)
assert named_dataset(EX.Foo) |> named_dataset?(iri(EX.Foo)) assert named_dataset(EX.Foo) |> named_dataset?(iri(EX.Foo))
end end

View file

@ -23,7 +23,7 @@ defmodule RDF.GraphTest do
test "creating an empty graph with a coercible graph name" do test "creating an empty graph with a coercible graph name" do
assert named_graph("http://example.com/graph/GraphName") assert named_graph("http://example.com/graph/GraphName")
|> named_graph?(iri("http://example.com/graph/GraphName")) |> named_graph?(~I<http://example.com/graph/GraphName>)
assert named_graph(EX.Foo) |> named_graph?(iri(EX.Foo)) assert named_graph(EX.Foo) |> named_graph?(iri(EX.Foo))
end end

View file

@ -9,8 +9,6 @@ defmodule RDF.ListTest do
use RDF.Vocabulary.Namespace use RDF.Vocabulary.Namespace
defvocab EX, base_iri: "http://example.org/#", terms: [], strict: false
setup do setup do
{:ok, {:ok,
empty: RDF.List.new(RDF.nil(), Graph.new()), empty: RDF.List.new(RDF.nil(), Graph.new()),

View file

@ -0,0 +1,56 @@
defmodule RDF.Namespace.IRITest do
use RDF.Test.Case
doctest RDF.Namespace.IRI
import RDF.Namespace.IRI
describe "iri/1" do
test "with a property function from a vocabulary namespace" do
assert iri(EX.foo()) == EX.foo()
assert iri(RDF.NS.OWL.sameAs()) == RDF.NS.OWL.sameAs()
end
test "with a term atom from a vocabulary namespace" do
assert iri(EX.Foo) == RDF.iri(EX.Foo)
end
test "constant function calls from non-vocabulary namespace module results in a compile error" do
assert_raise ArgumentError, ~r[forbidden expression in RDF.Guard.iri/1], fn ->
ast =
quote do
import RDF.Guards
iri(Mix.env())
end
Code.eval_quoted(ast, [], __ENV__)
end
end
test "other forms result in a compile error" do
assert_raise ArgumentError, ~r[forbidden expression in RDF.Guard.iri/1], fn ->
ast =
quote do
import RDF.Guards
var = ~I<http://example.com>
iri(var)
end
Code.eval_quoted(ast, [], __ENV__)
end
end
test "in pattern matches" do
assert (case EX.foo() do
iri(EX.foo()) -> "match"
_ -> {:mismatch, iri(EX.foo())}
end) == "match"
assert (case RDF.iri(EX.Bar) do
iri(EX.Bar) -> "match"
_ -> {:mismatch, iri(EX.Bar)}
end) == "match"
end
end
end

View file

@ -13,6 +13,13 @@ defmodule RDF.SigilsTest do
test "escaping" do test "escaping" do
assert ~I<http://example.com/f\no> == RDF.iri("http://example.com/f\\no") assert ~I<http://example.com/f\no> == RDF.iri("http://example.com/f\\no")
end end
test "in pattern matches" do
assert (case RDF.iri("http://example.com/foo") do
~I<http://example.com/foo> -> "match"
_ -> :mismatch
end) == "match"
end
end end
describe "~i sigil" do describe "~i sigil" do