Compare commits
5 commits
7325f0d75b
...
c16453fb37
Author | SHA1 | Date | |
---|---|---|---|
FloatingGhost | c16453fb37 | ||
71f710e44e | |||
72180ab669 | |||
0b0b42e2a1 | |||
370a3a3568 |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
@ -25,22 +25,25 @@ jobs:
|
|||
no-warnings-as-errors: true
|
||||
- pair:
|
||||
elixir: 1.12.3
|
||||
otp: 24.3
|
||||
otp: 24.3.4
|
||||
# Although Elixir 1.12 supports this feature, we're expecting errors since the code to suppress
|
||||
# undefined-function-warnings on RDF.Graph.build blocks relies on Elixir 1.13 and is disabled
|
||||
# partially on older versions.
|
||||
no-warnings-as-errors: true
|
||||
- pair:
|
||||
elixir: 1.13.3
|
||||
elixir: 1.13.4
|
||||
otp: 23.3
|
||||
- pair:
|
||||
elixir: 1.13.3
|
||||
elixir: 1.13.4
|
||||
otp: 24.3
|
||||
- pair:
|
||||
elixir: 1.13.4
|
||||
otp: 25.0
|
||||
lint: lint
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: erlef/setup-elixir@v1
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{matrix.pair.otp}}
|
||||
elixir-version: ${{matrix.pair.elixir}}
|
||||
|
|
|
@ -12,13 +12,17 @@ The generated namespaces are much more flexible now and compile faster.
|
|||
|
||||
### Added
|
||||
|
||||
- `RDF.Namespace` builders `defnamespace/3` and `create/4`
|
||||
- `RDF.Vocabulary.Namespace.create/5` for dynamic creation of `RDF.Vocabulary.Namespace`s
|
||||
- `RDF.Namespace` builders `defnamespace/3` and `create/4`
|
||||
- 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.Graph.build/2` now supports the creation of ad-hoc vocabulary namespaces
|
||||
with a `@prefix` declaration providing the URI of the namespace as a string
|
||||
with a `@prefix` declaration providing the URI of the namespace as a string
|
||||
- The `case_violations` option of `defvocab` now supports an `:auto_fix` option
|
||||
which adapts the first letter of violating term accordingly. It also supports
|
||||
custom handler functions, either as an inline function or as a function on a
|
||||
separate module.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ defmodule RDF.Vocabulary.Namespace.CaseValidation do
|
|||
import RDF.Vocabulary, only: [term_to_iri: 2]
|
||||
import RDF.Utils, only: [downcase?: 1]
|
||||
|
||||
import RDF.Vocabulary.Namespace.TermMapping,
|
||||
only: [normalize_term: 1, normalize_aliased_term: 1]
|
||||
|
||||
alias RDF.Vocabulary.ResourceClassifier
|
||||
|
||||
def validate_case!(terms_and_ignored, nil, _, _, _), do: terms_and_ignored
|
||||
|
@ -60,6 +63,10 @@ defmodule RDF.Vocabulary.Namespace.CaseValidation do
|
|||
)
|
||||
end
|
||||
|
||||
defp do_handle_case_violations(terms_and_ignored, grouped_violations, _, _)
|
||||
when map_size(grouped_violations) == 0,
|
||||
do: terms_and_ignored
|
||||
|
||||
defp do_handle_case_violations(_, violations, :fail, base_iri) do
|
||||
resource_name_violations = fn violations ->
|
||||
violations
|
||||
|
@ -138,6 +145,59 @@ defmodule RDF.Vocabulary.Namespace.CaseValidation do
|
|||
terms_and_ignored
|
||||
end
|
||||
|
||||
defp do_handle_case_violations(terms_and_ignored, violation_groups, :auto_fix, base_iri) do
|
||||
do_handle_case_violations(terms_and_ignored, violation_groups, &auto_fix_term/2, base_iri)
|
||||
end
|
||||
|
||||
defp do_handle_case_violations(terms_and_ignored, violation_groups, fun, base_iri)
|
||||
when is_function(fun) do
|
||||
{alias_violations, term_violations} =
|
||||
Map.split(violation_groups, [:capitalized_alias, :lowercased_alias])
|
||||
|
||||
do_handle_case_violations(terms_and_ignored, alias_violations, :fail, base_iri)
|
||||
|
||||
Enum.reduce(term_violations, terms_and_ignored, fn {group, violations}, terms_and_ignored ->
|
||||
type =
|
||||
case group do
|
||||
:capitalized_term -> :property
|
||||
:lowercased_term -> :resource
|
||||
end
|
||||
|
||||
Enum.reduce(violations, terms_and_ignored, fn {term, _}, {terms, ignored_terms} ->
|
||||
case fun.(type, Atom.to_string(term)) do
|
||||
:ignore ->
|
||||
{Map.delete(terms, term), MapSet.put(ignored_terms, term)}
|
||||
|
||||
{:error, error} ->
|
||||
raise error
|
||||
|
||||
{:ok, alias} ->
|
||||
{Map.put(terms, normalize_term(alias), normalize_aliased_term(term)), ignored_terms}
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_handle_case_violations(terms_and_ignored, violation_groups, {mod, fun}, base_iri)
|
||||
when is_atom(mod) and is_atom(fun) do
|
||||
do_handle_case_violations(
|
||||
terms_and_ignored,
|
||||
violation_groups,
|
||||
&apply(mod, fun, [&1, &2]),
|
||||
base_iri
|
||||
)
|
||||
end
|
||||
|
||||
defp do_handle_case_violations(terms_and_ignored, violation_groups, {mod, fun, args}, base_iri)
|
||||
when is_atom(mod) and is_atom(fun) and is_list(args) do
|
||||
do_handle_case_violations(
|
||||
terms_and_ignored,
|
||||
violation_groups,
|
||||
&apply(mod, fun, [&1, &2 | args]),
|
||||
base_iri
|
||||
)
|
||||
end
|
||||
|
||||
defp case_violation_warning(:capitalized_term, term, _, base_iri) do
|
||||
IO.warn("'#{term_to_iri(base_iri, term)}' is a capitalized property")
|
||||
end
|
||||
|
@ -153,4 +213,11 @@ defmodule RDF.Vocabulary.Namespace.CaseValidation do
|
|||
defp case_violation_warning(:lowercased_alias, term, _, _) do
|
||||
IO.warn("lowercased alias '#{term}' for a non-property resource")
|
||||
end
|
||||
|
||||
defp auto_fix_term(:property, term) do
|
||||
{first, rest} = String.next_grapheme(term)
|
||||
{:ok, String.downcase(first) <> rest}
|
||||
end
|
||||
|
||||
defp auto_fix_term(:resource, term), do: {:ok, :string.titlecase(term)}
|
||||
end
|
||||
|
|
|
@ -72,12 +72,12 @@ defmodule RDF.Vocabulary.Namespace.TermMapping do
|
|||
end)
|
||||
end
|
||||
|
||||
defp normalize_term(term) when is_atom(term), do: term
|
||||
defp normalize_term(term) when is_binary(term), do: String.to_atom(term)
|
||||
defp normalize_term(term), do: raise(RDF.Namespace.InvalidTermError, inspect(term))
|
||||
def normalize_term(term) when is_atom(term), do: term
|
||||
def normalize_term(term) when is_binary(term), do: String.to_atom(term)
|
||||
def normalize_term(term), do: raise(RDF.Namespace.InvalidTermError, inspect(term))
|
||||
|
||||
defp normalize_aliased_term(term) when is_binary(term), do: term
|
||||
defp normalize_aliased_term(term) when is_atom(term), do: Atom.to_string(term)
|
||||
def normalize_aliased_term(term) when is_binary(term), do: term
|
||||
def normalize_aliased_term(term) when is_atom(term), do: Atom.to_string(term)
|
||||
|
||||
def normalize_ignored_terms(terms), do: MapSet.new(terms, &normalize_term/1)
|
||||
|
||||
|
|
|
@ -22,22 +22,22 @@ defmodule RDF.XSD.Decimal do
|
|||
|
||||
@doc false
|
||||
def min_inclusive_conform?(min_inclusive, value, _lexical) do
|
||||
not (D.cmp(value, D.new(min_inclusive)) == :lt)
|
||||
not (D.compare(value, D.new(min_inclusive)) == :lt)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def max_inclusive_conform?(max_inclusive, value, _lexical) do
|
||||
not (D.cmp(value, D.new(max_inclusive)) == :gt)
|
||||
not (D.compare(value, D.new(max_inclusive)) == :gt)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def min_exclusive_conform?(min_exclusive, value, _lexical) do
|
||||
D.cmp(value, D.new(min_exclusive)) == :gt
|
||||
D.compare(value, D.new(min_exclusive)) == :gt
|
||||
end
|
||||
|
||||
@doc false
|
||||
def max_exclusive_conform?(max_exclusive, value, _lexical) do
|
||||
D.cmp(value, D.new(max_exclusive)) == :lt
|
||||
D.compare(value, D.new(max_exclusive)) == :lt
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -61,7 +61,8 @@ defmodule RDF.XSD.Decimal do
|
|||
@invalid_value
|
||||
else
|
||||
case D.parse(lexical) do
|
||||
{:ok, decimal} -> elixir_mapping(decimal, opts)
|
||||
{decimal, ""} -> elixir_mapping(decimal, opts)
|
||||
{_, _} -> @invalid_value
|
||||
:error -> @invalid_value
|
||||
end
|
||||
end
|
||||
|
@ -74,6 +75,8 @@ defmodule RDF.XSD.Decimal do
|
|||
def elixir_mapping(%D{coef: coef}, _) when coef in ~w[qNaN sNaN inf]a,
|
||||
do: @invalid_value
|
||||
|
||||
def elixir_mapping(%D{coef: :NaN}, _), do: @invalid_value
|
||||
|
||||
def elixir_mapping(%D{} = decimal, _),
|
||||
do: canonical_decimal(decimal)
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ defmodule RDF.XSD.Numeric do
|
|||
|
||||
def do_compare(_, _), do: nil
|
||||
|
||||
defp compare_decimal_value(%D{} = left, %D{} = right), do: D.cmp(left, right)
|
||||
defp compare_decimal_value(%D{} = left, %D{} = right), do: D.compare(left, right)
|
||||
|
||||
defp compare_decimal_value(%D{} = left, right),
|
||||
do: compare_decimal_value(left, new_decimal(right))
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -70,7 +70,7 @@ defmodule RDF.Mixfile do
|
|||
|
||||
defp deps do
|
||||
[
|
||||
{:decimal, "~> 1.5"},
|
||||
{:decimal, "~> 2.0"},
|
||||
{:protocol_ex, "~> 0.4.4"},
|
||||
{:elixir_uuid, "~> 1.2", optional: true},
|
||||
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -3,7 +3,7 @@
|
|||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
|
||||
"credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"},
|
||||
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
|
||||
|
|
|
@ -34,6 +34,16 @@ defmodule RDF.Vocabulary.NamespaceTest do
|
|||
@compile {:no_warn_undefined,
|
||||
RDF.Vocabulary.NamespaceTest.IgnoredAliasTest.ExampleIgnoredCapitalizedAlias}
|
||||
|
||||
defmodule ExampleCaseViolationHandler do
|
||||
def fix(_, "baazTest"), do: :ignore
|
||||
def fix(:property, term), do: {:ok, String.downcase(term)}
|
||||
def fix(:resource, term), do: {:ok, String.upcase(term)}
|
||||
|
||||
def fix(_, "baazTest", _), do: raise("this should not happen since we have an alias defined")
|
||||
def fix(:property, term, arg), do: {:ok, String.downcase(term) <> arg}
|
||||
def fix(:resource, term, arg), do: {:ok, String.upcase(term) <> arg}
|
||||
end
|
||||
|
||||
defmodule TestNS do
|
||||
use RDF.Vocabulary.Namespace
|
||||
|
||||
|
@ -128,6 +138,56 @@ defmodule RDF.Vocabulary.NamespaceTest do
|
|||
defvocab ExampleWithSynonymImplicitAliases,
|
||||
base_iri: "http://example.com/ex#",
|
||||
terms: [foo: "bar", baz: "bar", Foo: "Bar", Baz: "Bar"]
|
||||
|
||||
defvocab ExampleWithAutoFixedCaseViolations,
|
||||
base_iri: "http://example.com/ex#",
|
||||
case_violations: :auto_fix,
|
||||
data:
|
||||
RDF.Graph.new([
|
||||
{~I<http://example.com/ex#FooTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#barTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#bazTest>, RDF.type(), RDFS.Resource},
|
||||
{~I<http://example.com/ex#baazTest>, RDF.type(), RDFS.Resource}
|
||||
]),
|
||||
alias: [BaazAlias: :baazTest]
|
||||
|
||||
defvocab ExampleWithCustomInlineCaseViolationFunction,
|
||||
base_iri: "http://example.com/ex#",
|
||||
case_violations: fn
|
||||
_, "baazTest" -> :ignore
|
||||
:property, term -> {:ok, Macro.underscore(term)}
|
||||
:resource, term -> {:ok, Macro.camelize(term)}
|
||||
end,
|
||||
data:
|
||||
RDF.Graph.new([
|
||||
{~I<http://example.com/ex#FooTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#barTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#bazTest>, RDF.type(), RDFS.Resource},
|
||||
{~I<http://example.com/ex#baazTest>, RDF.type(), RDFS.Resource}
|
||||
])
|
||||
|
||||
defvocab ExampleWithCustomExternalCaseViolationFunction,
|
||||
base_iri: "http://example.com/ex#",
|
||||
case_violations: {ExampleCaseViolationHandler, :fix},
|
||||
data:
|
||||
RDF.Graph.new([
|
||||
{~I<http://example.com/ex#FooTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#barTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#bazTest>, RDF.type(), RDFS.Resource},
|
||||
{~I<http://example.com/ex#baazTest>, RDF.type(), RDFS.Resource}
|
||||
])
|
||||
|
||||
defvocab ExampleWithCustomExternalCaseViolationFunctionWithArgs,
|
||||
base_iri: "http://example.com/ex#",
|
||||
case_violations: {ExampleCaseViolationHandler, :fix, ["arg"]},
|
||||
data:
|
||||
RDF.Graph.new([
|
||||
{~I<http://example.com/ex#FooTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#barTest>, RDF.type(), RDF.Property},
|
||||
{~I<http://example.com/ex#bazTest>, RDF.type(), RDFS.Resource},
|
||||
{~I<http://example.com/ex#baazTest>, RDF.type(), RDFS.Resource}
|
||||
]),
|
||||
alias: [BaazTest: :baazTest]
|
||||
end
|
||||
|
||||
describe "defvocab with bad base iri" do
|
||||
|
@ -750,6 +810,61 @@ defmodule RDF.Vocabulary.NamespaceTest do
|
|||
])
|
||||
end
|
||||
end
|
||||
|
||||
test "auto_fix case violations" do
|
||||
alias TestNS.ExampleWithAutoFixedCaseViolations, as: Example
|
||||
|
||||
assert Example.fooTest() == RDF.iri(Example.__base_iri__() <> "FooTest")
|
||||
assert Example.barTest() == RDF.iri(Example.__base_iri__() <> "barTest")
|
||||
assert RDF.iri(Example.BazTest) == RDF.iri(Example.__base_iri__() <> "bazTest")
|
||||
assert RDF.iri(Example.BaazAlias) == RDF.iri(Example.__base_iri__() <> "baazTest")
|
||||
end
|
||||
|
||||
test "case violation function (inline)" do
|
||||
alias TestNS.ExampleWithCustomInlineCaseViolationFunction, as: Example
|
||||
|
||||
assert Example.foo_test() == RDF.iri(Example.__base_iri__() <> "FooTest")
|
||||
assert Example.barTest() == RDF.iri(Example.__base_iri__() <> "barTest")
|
||||
assert RDF.iri(Example.BazTest) == RDF.iri(Example.__base_iri__() <> "bazTest")
|
||||
|
||||
refute :baazTest in Example.__terms__()
|
||||
end
|
||||
|
||||
test "case violation function (external)" do
|
||||
alias TestNS.ExampleWithCustomExternalCaseViolationFunction, as: Example
|
||||
|
||||
assert Example.footest() == RDF.iri(Example.__base_iri__() <> "FooTest")
|
||||
assert Example.barTest() == RDF.iri(Example.__base_iri__() <> "barTest")
|
||||
assert RDF.iri(Example.BAZTEST) == RDF.iri(Example.__base_iri__() <> "bazTest")
|
||||
|
||||
refute :baazTest in Example.__terms__()
|
||||
end
|
||||
|
||||
test "case violation function (external; with args)" do
|
||||
alias TestNS.ExampleWithCustomExternalCaseViolationFunctionWithArgs, as: Example
|
||||
|
||||
assert Example.footestarg() == RDF.iri(Example.__base_iri__() <> "FooTest")
|
||||
assert Example.barTest() == RDF.iri(Example.__base_iri__() <> "barTest")
|
||||
assert RDF.iri(Example.BAZTESTarg) == RDF.iri(Example.__base_iri__() <> "bazTest")
|
||||
assert RDF.iri(Example.BaazTest) == RDF.iri(Example.__base_iri__() <> "baazTest")
|
||||
end
|
||||
|
||||
test "case violation in aliases fail with case violation function" do
|
||||
assert_raise RDF.Namespace.InvalidTermError, ~r/Foo/s, fn ->
|
||||
defmodule NSCaseViolationInAlias do
|
||||
use RDF.Vocabulary.Namespace
|
||||
|
||||
defvocab ExampleWithCustomCaseViolationFunction,
|
||||
base_iri: "http://example.com/ex#",
|
||||
case_violations: {ExampleCaseViolationHandler, :fix},
|
||||
data:
|
||||
RDF.Graph.new([
|
||||
{~I<http://example.com/ex#FooTest>, RDF.type(), RDF.Property}
|
||||
]),
|
||||
alias: [Foo: :FooTest]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "defvocab ignore terms" do
|
||||
|
|
Loading…
Reference in a new issue