Compare commits

...

5 commits

Author SHA1 Message Date
FloatingGhost c16453fb37 Migrate to decimal v2 2022-08-03 22:21:32 +01:00
Marcel Otto 71f710e44e
Merge pull request #14 from kianmeng/migrate-to-erlef-setup-beam
Migrate GitHub Actions to erlef/setup-beam
2022-06-28 00:44:14 +02:00
Kian-Meng Ang 72180ab669 Migrate GitHub Actions to erlef/setup-beam
See https://github.com/erlef/setup-beam/issues/20

We also bump to latest OTP 25.
2022-06-27 22:20:53 +08:00
Marcel Otto 0b0b42e2a1 Add :auto_fix for :case_validation option on defvocab 2022-06-27 00:21:03 +02:00
Marcel Otto 370a3a3568 Support for custom case-failure-handling functions on defvocab 2022-06-26 23:22:59 +02:00
9 changed files with 211 additions and 19 deletions

View file

@ -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}}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

@ -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},

View file

@ -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"},

View file

@ -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