Wrap the build block definition in a function
This also fixes the undefined-function-warnings raised in the previous version when using terms from non-strict vocabulary namespaces (incl. the auto-generated ad-hoc vocabulary namespaces).
This commit is contained in:
parent
eac696114f
commit
65bb0831b8
6 changed files with 182 additions and 137 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -53,7 +53,7 @@ jobs:
|
|||
- run: mix compile --warnings-as-errors
|
||||
if: ${{ matrix.lint }}
|
||||
|
||||
- run: MIX_ENV=test mix coveralls.github
|
||||
- run: MIX_ENV=test mix coveralls.github --warnings-as-errors
|
||||
|
||||
- name: Retrieve PLT Cache
|
||||
uses: actions/cache@v1
|
||||
|
|
|
@ -33,6 +33,11 @@ The generated namespaces are much more flexible now and compile faster.
|
|||
- When defining an alias for a term of vocabulary which would be invalid as an
|
||||
Elixir term, the original term is now implicitly ignored and won't any longer
|
||||
be returned by the `__terms__/0` function of a `RDF.Vocabulary.Namespace`.
|
||||
- `RDF.Graph.build/2` blocks are now wrapped in a function, so the aliases and
|
||||
import no longer affect the caller context. `alias`es in the caller context are
|
||||
still available in the build block, but `import`s not and must be reimported in
|
||||
the build block. Variables in the caller context are also no longer available
|
||||
build block.
|
||||
- `RDF.Data.merge/2` and `RDF.Data.equal?/2` are now commutative, i.e. structs
|
||||
which implement the `RDF.Data` protocol can be given also as the second argument
|
||||
(previously custom structs with `RDF.Data` protocol implementations always
|
||||
|
|
|
@ -342,5 +342,6 @@ defmodule RDF do
|
|||
defdelegate __base_iri__(), to: RDF.NS.RDF
|
||||
defdelegate __terms__(), to: RDF.NS.RDF
|
||||
defdelegate __iris__(), to: RDF.NS.RDF
|
||||
defdelegate __strict__(), to: RDF.NS.RDF
|
||||
defdelegate __resolve_term__(term), to: RDF.NS.RDF
|
||||
end
|
||||
|
|
|
@ -145,12 +145,7 @@ defmodule RDF.Graph do
|
|||
For a description of the DSL see [this guide](https://rdf-elixir.dev/rdf-ex/description-and-graph-dsl.html).
|
||||
"""
|
||||
defmacro build(opts \\ [], do: block) do
|
||||
Builder.build(
|
||||
block,
|
||||
__CALLER__,
|
||||
Builder.namespace_context_mod(__CALLER__),
|
||||
opts
|
||||
)
|
||||
Builder.build(block, __CALLER__, Builder.builder_mod(__CALLER__), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -18,21 +18,28 @@ defmodule RDF.Graph.Builder do
|
|||
end
|
||||
|
||||
def build(do_block, env, opts) do
|
||||
build(do_block, env, namespace_context_mod(env), opts)
|
||||
build(do_block, env, builder_mod(env), opts)
|
||||
end
|
||||
|
||||
def build({:__block__, _, block}, env, namespace_context_mod, opts) do
|
||||
def build({:__block__, _, block}, env, builder_mod, opts) do
|
||||
env_aliases = env_aliases(env)
|
||||
block = expand_aliased_modules(block, env_aliases)
|
||||
non_strict_ns = extract_non_strict_ns(block)
|
||||
{declarations, data} = Enum.split_with(block, &declaration?/1)
|
||||
{base, declarations} = extract_base(declarations, env_aliases)
|
||||
{base, declarations} = extract_base(declarations)
|
||||
base_string = base_string(base)
|
||||
data = resolve_relative_iris(data, base_string)
|
||||
declarations = resolve_relative_iris(declarations, base_string)
|
||||
{prefixes, ad_hoc_ns, declarations} = extract_prefixes(declarations, builder_mod, env)
|
||||
non_strict_ns = (non_strict_ns ++ ad_hoc_ns) |> Enum.uniq()
|
||||
|
||||
{prefixes, declarations} =
|
||||
extract_prefixes(declarations, env_aliases, namespace_context_mod, env)
|
||||
|
||||
mod_body =
|
||||
quote do
|
||||
for mod <- unquote(non_strict_ns) do
|
||||
@compile {:no_warn_undefined, mod}
|
||||
end
|
||||
|
||||
def build(opts) do
|
||||
alias RDF.XSD
|
||||
alias RDF.NS.{RDFS, OWL}
|
||||
|
||||
|
@ -43,15 +50,22 @@ defmodule RDF.Graph.Builder do
|
|||
|
||||
RDF.Graph.Builder.do_build(
|
||||
unquote(data),
|
||||
unquote(opts),
|
||||
opts,
|
||||
unquote(prefixes),
|
||||
unquote(base_string)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def build(single, env, namespace_context_mod, opts) do
|
||||
build({:__block__, [], List.wrap(single)}, env, namespace_context_mod, opts)
|
||||
Module.create(builder_mod, mod_body, Macro.Env.location(env))
|
||||
|
||||
quote do
|
||||
apply(unquote(builder_mod), :build, [unquote(opts)])
|
||||
end
|
||||
end
|
||||
|
||||
def build(single, env, builder_mod, opts) do
|
||||
build({:__block__, [], List.wrap(single)}, env, builder_mod, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -60,8 +74,8 @@ defmodule RDF.Graph.Builder do
|
|||
|> Graph.add(Enum.filter(data, &rdf?/1))
|
||||
end
|
||||
|
||||
def namespace_context_mod(env) do
|
||||
Module.concat(env.module, "GraphBuilderNS#{random_number()}")
|
||||
def builder_mod(env) do
|
||||
Module.concat(env.module, "GraphBuilder#{random_number()}")
|
||||
end
|
||||
|
||||
defp graph_opts(opts, prefixes, base) do
|
||||
|
@ -107,14 +121,11 @@ defmodule RDF.Graph.Builder do
|
|||
end)
|
||||
end
|
||||
|
||||
defp extract_base(declarations, env_aliases) do
|
||||
defp extract_base(declarations) do
|
||||
{base, declarations} =
|
||||
Enum.reduce(declarations, {nil, []}, fn
|
||||
{:@, line, [{:base, _, [{:__aliases__, _, ns}] = aliases}]}, {_, declarations} ->
|
||||
{
|
||||
ns |> expand_module(env_aliases) |> Module.concat(),
|
||||
[{:alias, line, aliases} | declarations]
|
||||
}
|
||||
{Module.concat(ns), [{:alias, line, aliases} | declarations]}
|
||||
|
||||
{:@, _, [{:base, _, [base]}]}, {_, declarations} ->
|
||||
{base, declarations}
|
||||
|
@ -126,49 +137,53 @@ defmodule RDF.Graph.Builder do
|
|||
{base, Enum.reverse(declarations)}
|
||||
end
|
||||
|
||||
defp extract_prefixes(declarations, env_aliases, namespace_context_mod, env) do
|
||||
{prefixes, declarations} =
|
||||
Enum.reduce(declarations, {[], []}, fn
|
||||
{:@, line, [{:prefix, _, [{:__aliases__, _, ns}] = aliases}]}, {prefixes, declarations} ->
|
||||
defp extract_prefixes(declarations, builder_mod, env) do
|
||||
{prefixes, ad_hoc_ns, declarations} =
|
||||
Enum.reduce(declarations, {[], [], []}, fn
|
||||
{:@, line, [{:prefix, _, [{:__aliases__, _, ns}] = aliases}]},
|
||||
{prefixes, ad_hoc_ns, declarations} ->
|
||||
{
|
||||
[prefix(ns, env_aliases) | prefixes],
|
||||
[prefix(ns) | prefixes],
|
||||
ad_hoc_ns,
|
||||
[{:alias, line, aliases} | declarations]
|
||||
}
|
||||
|
||||
{:@, line, [{:prefix, _, [[{prefix, {:__aliases__, _, ns} = aliases}]]}]},
|
||||
{prefixes, declarations} ->
|
||||
{prefixes, ad_hoc_ns, declarations} ->
|
||||
{
|
||||
[prefix(prefix, ns, env_aliases) | prefixes],
|
||||
[prefix(prefix, ns) | prefixes],
|
||||
ad_hoc_ns,
|
||||
[{:alias, line, [aliases]} | declarations]
|
||||
}
|
||||
|
||||
{:@, line, [{:prefix, _, [[{prefix, uri}]]}]}, {prefixes, declarations}
|
||||
{:@, line, [{:prefix, _, [[{prefix, uri}]]}]}, {prefixes, ad_hoc_ns, declarations}
|
||||
when is_binary(uri) ->
|
||||
ns = ad_hoc_namespace(prefix, uri, namespace_context_mod, env)
|
||||
ns = ad_hoc_namespace(prefix, uri, builder_mod, env)
|
||||
|
||||
{
|
||||
[prefix(prefix, ns, env_aliases) | prefixes],
|
||||
[prefix(prefix, ns) | prefixes],
|
||||
[Module.concat(ns) | ad_hoc_ns],
|
||||
[{:alias, line, [{:__aliases__, line, ns}]} | declarations]
|
||||
}
|
||||
|
||||
{:@, _, [{:prefix, _, _}]} = expr, _ ->
|
||||
raise Error, "invalid @prefix expression:\n\t#{Macro.to_string(expr)}"
|
||||
|
||||
declaration, {prefixes, declarations} ->
|
||||
{prefixes, [declaration | declarations]}
|
||||
declaration, {prefixes, ad_hoc_ns, declarations} ->
|
||||
{prefixes, ad_hoc_ns, [declaration | declarations]}
|
||||
end)
|
||||
|
||||
{prefixes, Enum.reverse(declarations)}
|
||||
{prefixes, ad_hoc_ns, Enum.reverse(declarations)}
|
||||
end
|
||||
|
||||
defp prefix(namespace, env_aliases) do
|
||||
defp prefix(namespace) do
|
||||
namespace
|
||||
|> determine_prefix()
|
||||
|> prefix(namespace, env_aliases)
|
||||
|> prefix(namespace)
|
||||
end
|
||||
|
||||
defp prefix(prefix, namespace, env_aliases) do
|
||||
{prefix, namespace |> expand_module(env_aliases) |> Module.concat()}
|
||||
defp prefix(prefix, namespace) do
|
||||
{prefix, Module.concat(namespace)}
|
||||
end
|
||||
|
||||
defp determine_prefix(namespace) do
|
||||
|
@ -180,9 +195,9 @@ defmodule RDF.Graph.Builder do
|
|||
|> String.to_atom()
|
||||
end
|
||||
|
||||
defp ad_hoc_namespace(prefix, uri, namespace_context_mod, env) do
|
||||
defp ad_hoc_namespace(prefix, uri, builder_mod, env) do
|
||||
{:module, module, _, _} =
|
||||
namespace_context_mod
|
||||
builder_mod
|
||||
|> Module.concat(prefix |> Atom.to_string() |> Macro.camelize())
|
||||
|> Vocabulary.Namespace.create!(uri, [], env, strict: false)
|
||||
|
||||
|
@ -211,6 +226,19 @@ defmodule RDF.Graph.Builder do
|
|||
raise Error, message: "invalid RDF data: #{inspect(invalid)}"
|
||||
end
|
||||
|
||||
defp expand_aliased_modules(ast, env_aliases) do
|
||||
Macro.prewalk(ast, fn
|
||||
{:__aliases__, [alias: false], _} = alias ->
|
||||
alias
|
||||
|
||||
{:__aliases__, _, module} ->
|
||||
{:__aliases__, [alias: false], expand_module(module, env_aliases)}
|
||||
|
||||
other ->
|
||||
other
|
||||
end)
|
||||
end
|
||||
|
||||
defp expand_module([first | rest] = module, env_aliases) do
|
||||
if full = env_aliases[first] do
|
||||
full ++ rest
|
||||
|
@ -233,6 +261,23 @@ defmodule RDF.Graph.Builder do
|
|||
String.to_atom(short)
|
||||
end
|
||||
|
||||
defp extract_non_strict_ns(block) do
|
||||
modules =
|
||||
block
|
||||
|> Macro.prewalker()
|
||||
|> Enum.reduce([], fn
|
||||
{:__aliases__, _, mod}, modules -> [Module.concat(mod) | modules]
|
||||
_, modules -> modules
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|
||||
for module <- modules, non_strict_vocab_namespace?(module), do: module
|
||||
end
|
||||
|
||||
defp non_strict_vocab_namespace?(mod) do
|
||||
Vocabulary.Namespace.vocabulary_namespace?(mod) and not mod.__strict__()
|
||||
end
|
||||
|
||||
defp random_number do
|
||||
:erlang.unique_integer([:positive])
|
||||
end
|
||||
|
|
|
@ -18,6 +18,7 @@ defmodule RDF.Graph.BuilderTest do
|
|||
|
||||
@compile {:no_warn_undefined, __MODULE__.TestNS.EX}
|
||||
@compile {:no_warn_undefined, __MODULE__.TestNS.Custom}
|
||||
@compile {:no_warn_undefined, RDF.Test.Case.EX}
|
||||
|
||||
alias TestNS.EX
|
||||
alias RDF.NS
|
||||
|
@ -317,51 +318,53 @@ defmodule RDF.Graph.BuilderTest do
|
|||
end
|
||||
|
||||
test "RDF.XSD is aliased" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
EX.S |> EX.p(XSD.byte(42))
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph == RDF.graph(EX.S |> EX.p(RDF.XSD.byte(42)))
|
||||
end
|
||||
|
||||
test "default aliases" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
OWL.Class |> RDFS.subClassOf(RDFS.Class)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph == RDF.graph(NS.OWL.Class |> NS.RDFS.subClassOf(NS.RDFS.Class))
|
||||
end
|
||||
|
||||
test "alias" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
alias TestNS.Custom
|
||||
Custom.S |> Custom.p(Custom.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph == RDF.graph(TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O))
|
||||
end
|
||||
|
||||
test "import" do
|
||||
# we're wrapping this in a function to isolate the import
|
||||
test "aliasing an already taken name" do
|
||||
graph =
|
||||
RDF.Graph.build do
|
||||
alias RDF.Test.Case.EX, as: EX2
|
||||
{EX2.S, EX.p(), EX2.foo()}
|
||||
end
|
||||
|
||||
quote do
|
||||
alias RDF.Test.Case.EX, as: EX2
|
||||
end
|
||||
|
||||
assert graph == RDF.graph(RDF.Test.Case.EX.S |> EX.p(RDF.Test.Case.EX.foo()))
|
||||
end
|
||||
|
||||
test "import" do
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
import TestNS.ImportTest
|
||||
EX.S |> foo(TestNS.ImportTest.Bar)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph == RDF.graph(EX.S |> TestNS.ImportTest.foo(TestNS.ImportTest.Bar))
|
||||
end
|
||||
|
@ -394,15 +397,12 @@ defmodule RDF.Graph.BuilderTest do
|
|||
|
||||
describe "@prefix" do
|
||||
test "for vocabulary namespace with explicit prefix" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
@prefix cust: TestNS.Custom
|
||||
|
||||
Custom.S |> Custom.p(Custom.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O),
|
||||
|
@ -411,15 +411,12 @@ defmodule RDF.Graph.BuilderTest do
|
|||
end
|
||||
|
||||
test "for vocabulary namespace with auto-generated prefix" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
@prefix TestNS.Custom
|
||||
|
||||
Custom.S |> Custom.p(Custom.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O),
|
||||
|
@ -428,15 +425,12 @@ defmodule RDF.Graph.BuilderTest do
|
|||
end
|
||||
|
||||
test "ad-hoc vocabulary namespace for URIs given as string" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
@prefix ad: "http://example.com/ad-hoc/"
|
||||
|
||||
Ad.S |> Ad.p(Ad.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(
|
||||
|
@ -450,9 +444,6 @@ defmodule RDF.Graph.BuilderTest do
|
|||
end
|
||||
|
||||
test "two ad-hoc vocabulary namespaces for the same URI in the same context" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
graph1 =
|
||||
RDF.Graph.build do
|
||||
@prefix ad: "http://example.com/ad-hoc/"
|
||||
|
@ -461,24 +452,33 @@ defmodule RDF.Graph.BuilderTest do
|
|||
Ad.S |> Ad.p(Ex1.O)
|
||||
end
|
||||
|
||||
graph2 =
|
||||
RDF.Graph.build do
|
||||
@prefix ad: "http://example.com/ad-hoc/"
|
||||
@prefix ex2: "http://example.com/ad-hoc/ex2#"
|
||||
|
||||
graph1
|
||||
|
||||
Ad.S |> Ad.p(Ex2.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
assert graph1 ==
|
||||
RDF.graph(
|
||||
[
|
||||
{
|
||||
RDF.iri("http://example.com/ad-hoc/S"),
|
||||
RDF.iri("http://example.com/ad-hoc/p"),
|
||||
RDF.iri("http://example.com/ad-hoc/ex1#O")
|
||||
},
|
||||
}
|
||||
],
|
||||
prefixes:
|
||||
RDF.default_prefixes(
|
||||
ad: "http://example.com/ad-hoc/",
|
||||
ex1: "http://example.com/ad-hoc/ex1#"
|
||||
)
|
||||
)
|
||||
|
||||
assert graph2 ==
|
||||
RDF.graph(
|
||||
[
|
||||
{
|
||||
RDF.iri("http://example.com/ad-hoc/S"),
|
||||
RDF.iri("http://example.com/ad-hoc/p"),
|
||||
|
@ -488,22 +488,18 @@ defmodule RDF.Graph.BuilderTest do
|
|||
prefixes:
|
||||
RDF.default_prefixes(
|
||||
ad: "http://example.com/ad-hoc/",
|
||||
ex1: "http://example.com/ad-hoc/ex1#",
|
||||
ex2: "http://example.com/ad-hoc/ex2#"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
test "merge with prefixes opt" do
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build prefixes: [custom: EX] do
|
||||
@prefix custom: TestNS.Custom
|
||||
|
||||
Custom.S |> Custom.p(Custom.O)
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O),
|
||||
|
@ -516,16 +512,13 @@ defmodule RDF.Graph.BuilderTest do
|
|||
test "with vocabulary namespace" do
|
||||
import RDF.Sigils
|
||||
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
(fn ->
|
||||
RDF.Graph.build do
|
||||
@base TestNS.Custom
|
||||
|
||||
~I<S> |> Custom.p(~I<O>)
|
||||
{~I<foo>, ~I<bar>, ~I<baz>}
|
||||
end
|
||||
end).()
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(
|
||||
|
@ -546,6 +539,8 @@ defmodule RDF.Graph.BuilderTest do
|
|||
{~I<#foo>, ~I<#bar>, ~I<#baz>}
|
||||
end
|
||||
|
||||
import RDF.Sigils
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(
|
||||
[
|
||||
|
@ -566,6 +561,8 @@ defmodule RDF.Graph.BuilderTest do
|
|||
{~I<#foo>, ~I<#bar>, ~I<#baz>}
|
||||
end
|
||||
|
||||
import RDF.Sigils
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(
|
||||
[
|
||||
|
@ -585,6 +582,8 @@ defmodule RDF.Graph.BuilderTest do
|
|||
~I<#S> |> EX.p(~I<#O>)
|
||||
end
|
||||
|
||||
import RDF.Sigils
|
||||
|
||||
assert graph ==
|
||||
RDF.graph(~I<http://example.com/base#S> |> EX.p(~I<http://example.com/base#O>),
|
||||
base_iri: "http://example.com/base"
|
||||
|
|
Loading…
Reference in a new issue