Merge pull request #6 from rustra/fix_dialyzer_warnings

* Fix type specs for structs
* Ignore 'Unknown function' Dialyzer warnings
* Add function signatures
* Specs improvements and fixes
* Add value type specs for RDF.Datatype
This commit is contained in:
Marcel Otto 2020-03-03 22:46:50 +01:00 committed by GitHub
commit a1071b949f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 497 additions and 89 deletions

17
.dialyzer_ignore Normal file
View file

@ -0,0 +1,17 @@
:0: Unknown function 'Elixir.RDF.Data.Atom':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.BitString':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Float':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Function':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Integer':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.List':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Map':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.PID':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Port':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Reference':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Data.Tuple':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.Function':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.List':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.Map':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.PID':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.Port':'__impl__'/1
:0: Unknown function 'Elixir.RDF.Term.Tuple':'__impl__'/1

View file

@ -6,14 +6,17 @@ defmodule RDF.BlankNode do
and <https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes>
"""
@type t :: %__MODULE__{
id: String.t
}
@enforce_keys [:id]
defstruct [:id]
@type t :: module
@doc """
Creates a `RDF.BlankNode` with an arbitrary internal id.
"""
@spec new :: t
def new,
do: new(make_ref())
@ -25,6 +28,7 @@ defmodule RDF.BlankNode do
iex> RDF.bnode(:foo)
%RDF.BlankNode{id: "foo"}
"""
@spec new(reference | String.t | atom | integer) :: t
def new(id)
def new(id) when is_binary(id),
@ -42,6 +46,7 @@ defmodule RDF.BlankNode do
Returns `nil` when the given arguments are not comparable as blank nodes.
"""
@spec equal_value?(t, t) :: boolean | nil
def equal_value?(left, right)
def equal_value?(%RDF.BlankNode{id: left}, %RDF.BlankNode{id: right}),

View file

@ -8,7 +8,7 @@ defmodule RDF.BlankNode.Generator.Algorithm do
@doc """
Returns the initial state of the algorithm.
"""
@callback init(opts :: map | Keyword.t() | nil) :: map
@callback init(opts :: map | keyword) :: map
@doc """
Generates a blank node.

View file

@ -13,21 +13,30 @@ defmodule RDF.Dataset do
"""
defstruct name: nil, graphs: %{}
@behaviour Access
alias RDF.{Graph, Description}
alias RDF.{Description, Graph, IRI, Statement}
import RDF.Statement
@type t :: module
@type graph_name :: IRI.t | nil
@type t :: %__MODULE__{
name: graph_name,
graphs: %{graph_name => Graph.t}
}
@type input :: Graph.input | t
@type update_graph_fun :: (Graph.t -> {Graph.t, input} | :pop)
defstruct name: nil, graphs: %{}
@doc """
Creates an empty unnamed `RDF.Dataset`.
"""
def new,
do: %RDF.Dataset{}
@spec new :: t
def new, do: %RDF.Dataset{}
@doc """
Creates an `RDF.Dataset`.
@ -44,6 +53,7 @@ defmodule RDF.Dataset do
RDF.Graph.new(name: EX.GraphName)
"""
@spec new(input | [input] | keyword) :: t
def new(data_or_options)
def new(data_or_options)
@ -73,6 +83,7 @@ defmodule RDF.Dataset do
- `name`: the name of the dataset to be created
"""
@spec new(input | [input], keyword) :: t
def new(data, options)
def new(%RDF.Dataset{} = graph, options) do
@ -93,6 +104,7 @@ defmodule RDF.Dataset do
destination graph to which the statements are added, ignoring the graph context
of given quads or the name of given graphs.
"""
@spec add(t, input | [input], boolean | nil) :: t
def add(dataset, statements, graph_context \\ false)
def add(dataset, statements, graph_context) when is_list(statements) do
@ -174,6 +186,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.put([{EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
RDF.Dataset.new([{EX.S1, EX.P1, EX.O1}, {EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
"""
@spec put(t, input | [input], Statement.coercible_graph_name | boolean | nil) :: t
def put(dataset, statements, graph_context \\ false)
def put(%RDF.Dataset{} = dataset, {subject, predicate, objects}, false),
@ -294,6 +307,7 @@ defmodule RDF.Dataset do
are deleted. If you want to delete only datasets with matching names, you can
use `RDF.Data.delete/2`.
"""
@spec delete(t, input | [input], Statement.coercible_graph_name | boolean | nil) :: t
def delete(dataset, statements, graph_context \\ false)
def delete(%RDF.Dataset{} = dataset, statements, graph_context) when is_list(statements) do
@ -357,6 +371,7 @@ defmodule RDF.Dataset do
@doc """
Deletes the given graph.
"""
@spec delete_graph(t, Statement.graph_name | [Statement.graph_name] | nil) :: t
def delete_graph(graph, graph_names)
def delete_graph(%RDF.Dataset{} = dataset, graph_names) when is_list(graph_names) do
@ -374,6 +389,7 @@ defmodule RDF.Dataset do
@doc """
Deletes the default graph.
"""
@spec delete_default_graph(t) :: t
def delete_default_graph(%RDF.Dataset{} = graph),
do: delete_graph(graph, nil)
@ -394,6 +410,7 @@ defmodule RDF.Dataset do
:error
"""
@impl Access
@spec fetch(t, Statement.graph_name | nil) :: {:ok, Graph.t} | :error
def fetch(%RDF.Dataset{graphs: graphs}, graph_name) do
Access.fetch(graphs, coerce_graph_name(graph_name))
end
@ -416,6 +433,7 @@ defmodule RDF.Dataset do
iex> RDF.Dataset.get(dataset, EX.Foo, :bar)
:bar
"""
@spec get(t, Statement.graph_name | nil, Graph.t | nil) :: Graph.t | nil
def get(%RDF.Dataset{} = dataset, graph_name, default \\ nil) do
case fetch(dataset, graph_name) do
{:ok, value} -> value
@ -426,12 +444,14 @@ defmodule RDF.Dataset do
@doc """
The graph with given name.
"""
@spec graph(t, Statement.graph_name | nil) :: Graph.t
def graph(%RDF.Dataset{graphs: graphs}, graph_name),
do: Map.get(graphs, coerce_graph_name(graph_name))
@doc """
The default graph of a `RDF.Dataset`.
"""
@spec default_graph(t) :: Graph.t
def default_graph(%RDF.Dataset{graphs: graphs}),
do: Map.get(graphs, nil, Graph.new)
@ -439,6 +459,7 @@ defmodule RDF.Dataset do
@doc """
The set of all graphs.
"""
@spec graphs(t) :: [Graph.t]
def graphs(%RDF.Dataset{graphs: graphs}), do: Map.values(graphs)
@ -465,6 +486,7 @@ defmodule RDF.Dataset do
{RDF.Graph.new({EX.S, EX.P, EX.O}, name: EX.Graph), RDF.Dataset.new({EX.S, EX.P, EX.NEW, EX.Graph})}
"""
@impl Access
@spec get_and_update(t, Statement.graph_name | nil, update_graph_fun) :: {Graph.t, input}
def get_and_update(%RDF.Dataset{} = dataset, graph_name, fun) do
with graph_context = coerce_graph_name(graph_name) do
case fun.(get(dataset, graph_context)) do
@ -482,6 +504,7 @@ defmodule RDF.Dataset do
@doc """
Pops an arbitrary statement from a `RDF.Dataset`.
"""
@spec pop(t) :: {Statement.t | nil, t}
def pop(dataset)
def pop(%RDF.Dataset{graphs: graphs} = dataset)
@ -515,6 +538,7 @@ defmodule RDF.Dataset do
{nil, dataset}
"""
@impl Access
@spec pop(t, Statement.coercible_graph_name) :: {Statement.t | nil, t}
def pop(%RDF.Dataset{name: name, graphs: graphs} = dataset, graph_name) do
case Access.pop(graphs, coerce_graph_name(graph_name)) do
{nil, _} ->
@ -538,6 +562,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.statement_count
3
"""
@spec statement_count(t) :: non_neg_integer
def statement_count(%RDF.Dataset{graphs: graphs}) do
Enum.reduce graphs, 0, fn ({_, graph}, count) ->
count + Graph.triple_count(graph)
@ -636,6 +661,7 @@ defmodule RDF.Dataset do
{RDF.iri(EX.S1), RDF.iri(EX.p2), RDF.iri(EX.O3)},
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
"""
@spec statements(t) :: [Statement.t]
def statements(%RDF.Dataset{graphs: graphs}) do
Enum.reduce graphs, [], fn ({_, graph}, all_statements) ->
statements = Graph.triples(graph)
@ -660,6 +686,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.include?(dataset, {EX.S1, EX.p1, EX.O1, EX.Graph})
true
"""
@spec include?(t, Statement.t, Statement.coercible_graph_name | nil) :: boolean
def include?(dataset, statement, graph_context \\ nil)
def include?(%RDF.Dataset{graphs: graphs}, triple = {_, _, _}, graph_context) do
@ -686,6 +713,7 @@ defmodule RDF.Dataset do
iex> RDF.Dataset.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Dataset.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.t, Statement.coercible_graph_name | nil) :: boolean
def describes?(%RDF.Dataset{graphs: graphs}, subject, graph_context \\ nil) do
with graph_context = coerce_graph_name(graph_context) do
if graph = graphs[graph_context] do
@ -708,6 +736,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.who_describes(dataset, EX.S1)
[nil, RDF.iri(EX.Graph1)]
"""
@spec who_describes(t, Statement.coercible_subject) :: [Graph.t]
def who_describes(%RDF.Dataset{graphs: graphs}, subject) do
with subject = coerce_subject(subject) do
graphs
@ -770,6 +799,7 @@ defmodule RDF.Dataset do
}
"""
@spec values(t, Statement.term_mapping) :: map
def values(dataset, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Dataset{graphs: graphs}, mapping) do
@ -785,6 +815,7 @@ defmodule RDF.Dataset do
Two `RDF.Dataset`s are considered to be equal if they contain the same triples
and have the same name.
"""
@spec equal?(t | any, t | any) :: boolean
def equal?(dataset1, dataset2)
def equal?(%RDF.Dataset{} = dataset1, %RDF.Dataset{} = dataset2) do

View file

@ -6,23 +6,25 @@ defmodule RDF.Datatype do
the validation, conversion and canonicalization of typed `RDF.Literal`s.
"""
alias RDF.Literal
alias RDF.{IRI, Literal}
alias RDF.Datatype.NS.XSD
@type t :: module
@doc """
The IRI of the datatype.
"""
@callback id :: RDF.IRI.t
@callback id :: IRI.t
@doc """
Produces the lexical form of a `RDF.Literal`.
"""
@callback lexical(literal :: RDF.Literal.t) :: any
@callback lexical(Literal.t) :: any
@doc """
Produces the lexical form of a value.
"""
@callback canonical_lexical(any) :: binary
@callback canonical_lexical(any) :: String.t | nil
@doc """
Produces the lexical form of an invalid value of a typed Literal.
@ -30,12 +32,12 @@ defmodule RDF.Datatype do
The default implementation of the `_using__` macro just returns `to_string`
representation of the value.
"""
@callback invalid_lexical(any) :: binary
@callback invalid_lexical(any) :: String.t | nil
@doc """
Produces the canonical form of a `RDF.Literal`.
"""
@callback canonical(RDF.Literal.t) :: RDF.Literal.t
@callback canonical(Literal.t) :: Literal.t | nil
@doc """
Converts a value into a proper native value.
@ -58,13 +60,13 @@ defmodule RDF.Datatype do
If the given literal is invalid or can not be converted into this datatype
`nil` is returned.
"""
@callback cast(RDF.Literal.t) :: RDF.Literal.t
@callback cast(Literal.t) :: Literal.t | nil
@doc """
Determines if the value of a `RDF.Literal` is a member of lexical value space of its datatype.
"""
@callback valid?(literal :: RDF.Literal.t) :: boolean
@callback valid?(literal :: Literal.t) :: boolean | nil
@doc """
Checks if the value of two `RDF.Literal`s of this datatype are equal.
@ -76,7 +78,7 @@ defmodule RDF.Datatype do
The default implementation of the `_using__` macro compares the values of the
`canonical/1` forms of the given literals of this datatype.
"""
@callback equal_value?(literal1 :: RDF.Literal.t, literal2 :: RDF.Literal.t) :: boolean | nil
@callback equal_value?(literal1 :: Literal.t, literal2 :: Literal.t) :: boolean | nil
@doc """
Compares two `RDF.Literal`s.
@ -92,7 +94,7 @@ defmodule RDF.Datatype do
The default implementation of the `_using__` macro compares the values of the
`canonical/1` forms of the given literals of this datatype.
"""
@callback compare(literal1 :: RDF.Literal.t, literal2 :: RDF.Literal.t) :: :lt | :gt | :eq | :indeterminate | nil
@callback compare(literal1 :: Literal.t, literal2 :: Literal.t) :: :lt | :gt | :eq | :indeterminate | nil
@lang_string RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
@ -113,21 +115,25 @@ defmodule RDF.Datatype do
@doc """
The mapping of IRIs of datatypes to their `RDF.Datatype`.
"""
@spec mapping :: %{IRI.t => t}
def mapping, do: @mapping
@doc """
The IRIs of all datatypes with a `RDF.Datatype` defined.
"""
@spec ids :: [IRI.t]
def ids, do: Map.keys(@mapping)
@doc """
All defined `RDF.Datatype` modules.
"""
@spec modules :: [t]
def modules, do: Map.values(@mapping)
@doc """
Returns the `RDF.Datatype` for a directly datatype IRI or the datatype IRI of a `RDF.Literal`.
"""
@spec get(Literal.t | IRI.t) :: t
def get(%Literal{datatype: id}), do: get(id)
def get(id), do: @mapping[id]

View file

@ -7,6 +7,8 @@ defmodule RDF.Boolean do
import RDF.Literal.Guards
@type value :: boolean
@impl RDF.Datatype
def convert(value, opts)

View file

@ -7,6 +7,8 @@ defmodule RDF.Date do
import RDF.Literal.Guards
@type value :: Date.t | {Date.t, String.t}
@grammar ~r/\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@xsd_datetime RDF.Datatype.NS.XSD.dateTime

View file

@ -7,6 +7,8 @@ defmodule RDF.DateTime do
import RDF.Literal.Guards
@type value :: DateTime.t | NaiveDateTime.t
@xsd_date RDF.Datatype.NS.XSD.date

View file

@ -8,8 +8,11 @@ defmodule RDF.Decimal do
alias Elixir.Decimal, as: D
@type value :: Decimal.t | :nan
@xsd_integer RDF.Datatype.NS.XSD.integer
@impl RDF.Datatype
def convert(value, opts)

View file

@ -7,6 +7,8 @@ defmodule RDF.Double do
import RDF.Literal.Guards
@type value :: float
def build_literal_by_value(value, opts) do
case convert(value, opts) do

View file

@ -7,6 +7,8 @@ defmodule RDF.Integer do
import RDF.Literal.Guards
@type value :: integer
@impl RDF.Datatype
def convert(value, opts)

View file

@ -35,11 +35,13 @@ defmodule RDF.Numeric do
@doc """
The list of all numeric datatypes.
"""
@dialyzer {:nowarn_function, types: 0}
def types(), do: MapSet.to_list(@types)
@doc """
Returns if a given datatype is a numeric datatype.
"""
@dialyzer {:nowarn_function, type?: 1}
def type?(type), do: MapSet.member?(@types, type)
@doc """

View file

@ -7,6 +7,8 @@ defmodule RDF.String do
alias RDF.Datatype.NS.XSD
@type value :: String.t
def new(value, opts) when is_list(opts),
do: new(value, Map.new(opts))

View file

@ -7,6 +7,8 @@ defmodule RDF.Time do
import RDF.Literal.Guards
@type value :: Time.t
@grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@tz_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/

View file

@ -10,35 +10,50 @@ defmodule RDF.Description do
- the `RDF.Data` protocol
"""
@enforce_keys [:subject]
defstruct subject: nil, predications: %{}
@behaviour Access
import RDF.Statement
alias RDF.{Statement, Triple}
@type predications :: %{Statement.predicate => %{Statement.object => nil}}
@type statements ::
{Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_predicate]}
| Statement.t
| predications
| t
@type t :: %__MODULE__{
subject: Statement.subject,
predications: predications
}
@enforce_keys [:subject]
defstruct subject: nil, predications: %{}
@type t :: module
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
When given a list of statements, the first one must contain a subject.
"""
@spec new(RDF.Statement.coercible_subject) :: RDF.Description.t
@spec new(Statement.coercible_subject | statements | [statements]) :: t
def new(subject)
def new({subject, predicate, object}),
do: new(subject) |> add(predicate, object)
def new([statement | more_statements]),
do: new(statement) |> add(more_statements)
def new(%RDF.Description{} = description),
def new(%__MODULE__{} = description),
do: description
def new(subject),
do: %RDF.Description{subject: coerce_subject(subject)}
do: %__MODULE__{subject: coerce_subject(subject)}
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(Statement.coercible_subject, statements | [statements]) :: t
def new(subject, {predicate, objects}),
do: new(subject) |> add(predicate, objects)
def new(subject, statements) when is_list(statements),
@ -51,8 +66,13 @@ defmodule RDF.Description do
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(
Statement.coercible_subject | statements | [statements],
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def new(%RDF.Description{} = description, predicate, objects),
do: RDF.Description.add(description, predicate, objects)
do: add(description, predicate, objects)
def new(subject, predicate, objects),
do: new(subject) |> add(predicate, objects)
@ -67,6 +87,11 @@ defmodule RDF.Description do
iex> RDF.Description.add(RDF.Description.new({EX.S, EX.P, EX.O1}), EX.P, [EX.O2, EX.O3])
RDF.Description.new([{EX.S, EX.P, EX.O1}, {EX.S, EX.P, EX.O2}, {EX.S, EX.P, EX.O3}])
"""
@spec add(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def add(description, predicate, objects)
def add(description, predicate, objects) when is_list(objects) do
@ -95,6 +120,7 @@ defmodule RDF.Description do
are added. As opposed to that `RDF.Data.merge/2` will produce a `RDF.Graph`
containing both descriptions.
"""
@spec add(t, statements | [statements]) :: t
def add(description, statements)
def add(description, {predicate, object}),
@ -139,6 +165,11 @@ defmodule RDF.Description do
iex> RDF.Description.put(RDF.Description.new({EX.S, EX.P1, EX.O1}), EX.P2, EX.O2)
RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
"""
@spec put(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def put(description, predicate, objects)
def put(%RDF.Description{subject: subject, predications: predications},
@ -170,6 +201,7 @@ defmodule RDF.Description do
...> RDF.Description.put(%{EX.P2 => [EX.O3, EX.O4]})
RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O3}, {EX.S, EX.P2, EX.O4}])
"""
@spec put(t, statements | [statements]) :: t
def put(description, statements)
def put(%RDF.Description{} = description, {predicate, object}),
@ -218,6 +250,11 @@ defmodule RDF.Description do
@doc """
Deletes statements from a `RDF.Description`.
"""
@spec delete(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def delete(description, predicate, objects)
def delete(description, predicate, objects) when is_list(objects) do
@ -255,6 +292,7 @@ defmodule RDF.Description do
are deleted. If you want to delete only a matching description subject, you can
use `RDF.Data.delete/2`.
"""
@spec delete(t, statements | [statements]) :: t
def delete(description, statements)
def delete(desc = %RDF.Description{}, {predicate, object}),
@ -291,6 +329,7 @@ defmodule RDF.Description do
@doc """
Deletes all statements with the given properties.
"""
@spec delete_predicates(t, Statement.coercible_predicate | [Statement.coercible_predicate]) :: t
def delete_predicates(description, properties)
def delete_predicates(%RDF.Description{} = description, properties) when is_list(properties) do
@ -322,6 +361,7 @@ defmodule RDF.Description do
:error
"""
@impl Access
@spec fetch(t, Statement.coercible_predicate) :: {:ok, [Statement.object]} | :error
def fetch(%RDF.Description{predications: predications}, predicate) do
with {:ok, objects} <- Access.fetch(predications, coerce_predicate(predicate)) do
{:ok, Map.keys(objects)}
@ -342,6 +382,7 @@ defmodule RDF.Description do
iex> RDF.Description.get(RDF.Description.new(EX.S), EX.foo, :bar)
:bar
"""
@spec get(t, Statement.coercible_predicate, any) :: [Statement.object] | any
def get(description = %RDF.Description{}, predicate, default \\ nil) do
case fetch(description, predicate) do
{:ok, value} -> value
@ -361,6 +402,7 @@ defmodule RDF.Description do
iex> RDF.Description.first(RDF.Description.new(EX.S), EX.foo)
nil
"""
@spec first(t, Statement.coercible_predicate) :: Statement.object | nil
def first(description = %RDF.Description{}, predicate) do
description
|> get(predicate, [])
@ -389,6 +431,12 @@ defmodule RDF.Description do
RDF.Description.new({EX.S, EX.p, EX.O})
"""
@spec update(
t,
Statement.coercible_predicate,
Statement.coercible_object | nil,
([Statement.Object] -> [Statement.Object])
) :: t
def update(description = %RDF.Description{}, predicate, initial \\ nil, fun) do
predicate = coerce_predicate(predicate)
@ -438,6 +486,11 @@ defmodule RDF.Description do
{[RDF.iri(EX.O1)], RDF.Description.new({EX.S, EX.P2, EX.O2})}
"""
@impl Access
@spec get_and_update(
t,
Statement.coercible_predicate,
([Statement.Object] -> {[Statement.Object], t} | :pop)
) :: {[Statement.Object], t}
def get_and_update(description = %RDF.Description{}, predicate, fun) do
with triple_predicate = coerce_predicate(predicate) do
case fun.(get(description, triple_predicate)) do
@ -452,6 +505,7 @@ defmodule RDF.Description do
@doc """
Pops an arbitrary triple from a `RDF.Description`.
"""
@spec pop(t) :: {Triple.t | [Statement.Object] | nil, t}
def pop(description)
def pop(description = %RDF.Description{predications: predications})
@ -505,6 +559,7 @@ defmodule RDF.Description do
...> RDF.Description.predicates
MapSet.new([EX.p1, EX.p2])
"""
@spec predicates(t) :: MapSet.t
def predicates(%RDF.Description{predications: predications}),
do: predications |> Map.keys |> MapSet.new
@ -524,12 +579,14 @@ defmodule RDF.Description do
...> ]) |> RDF.Description.objects
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)])
"""
@spec objects(t) :: MapSet.t
def objects(%RDF.Description{} = description),
do: objects(description, &RDF.resource?/1)
@doc """
The set of all resources used in the objects within a `RDF.Description` satisfying the given filter criterion.
"""
@spec objects(t, (Statement.object -> boolean)) :: MapSet.t
def objects(%RDF.Description{predications: predications}, filter_fn) do
Enum.reduce predications, MapSet.new, fn ({_, objects}, acc) ->
objects
@ -554,6 +611,7 @@ defmodule RDF.Description do
...> ]) |> RDF.Description.resources
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2, EX.p3])
"""
@spec resources(t) :: MapSet.t
def resources(description) do
description
|> objects
@ -563,6 +621,7 @@ defmodule RDF.Description do
@doc """
The list of all triples within a `RDF.Description`.
"""
@spec triples(t) :: keyword
def triples(description = %RDF.Description{}), do: Enum.to_list(description)
defdelegate statements(description), to: RDF.Description, as: :triples
@ -571,6 +630,7 @@ defmodule RDF.Description do
@doc """
Returns the number of statements of a `RDF.Description`.
"""
@spec count(t) :: non_neg_integer
def count(%RDF.Description{predications: predications}) do
Enum.reduce predications, 0,
fn ({_, objects}, count) -> count + Enum.count(objects) end
@ -580,6 +640,7 @@ defmodule RDF.Description do
@doc """
Checks if the given statement exists within a `RDF.Description`.
"""
@spec include?(t, statements) :: boolean
def include?(description, statement)
def include?(%RDF.Description{predications: predications},
@ -611,6 +672,7 @@ defmodule RDF.Description do
iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.subject) :: boolean
def describes?(%RDF.Description{subject: subject}, other_subject) do
with other_subject = coerce_subject(other_subject) do
subject == other_subject
@ -650,6 +712,7 @@ defmodule RDF.Description do
%{p: ["Foo"]}
"""
@spec values(t, Statement.term_mapping) :: map
def values(description, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Description{predications: predications}, mapping) do
@ -668,6 +731,7 @@ defmodule RDF.Description do
If `nil` is passed, the description is left untouched.
"""
@spec take(t, [Statement.coercible_predicate] | nil) :: t
def take(description, predicates)
def take(%RDF.Description{} = description, nil), do: description
@ -682,6 +746,7 @@ defmodule RDF.Description do
Two `RDF.Description`s are considered to be equal if they contain the same triples.
"""
@spec equal?(t, t) :: boolean
def equal?(description1, description2)
def equal?(%RDF.Description{} = description1, %RDF.Description{} = description2) do

View file

@ -6,9 +6,15 @@ defmodule RDF.Diff do
with `RDF.Graph`s of added and deleted statements.
"""
alias RDF.{Description, Graph}
@type t :: %__MODULE__{
additions: Graph.t,
deletions: Graph.t
}
defstruct [:additions, :deletions]
alias RDF.{Description, Graph}
@doc """
Creates a `RDF.Diff` struct.
@ -17,6 +23,7 @@ defmodule RDF.Diff do
`additions` and `deletions` keywords. The statements for the additions and
deletions can be provided in any form supported by the `RDF.Graph.new/1` function.
"""
@spec new(keyword) :: t
def new(diff \\ []) do
%__MODULE__{
additions: Keyword.get(diff, :additions) |> coerce_graph(),
@ -52,6 +59,8 @@ defmodule RDF.Diff do
deletions: RDF.graph({EX.S1, EX.p1, EX.O1})
}
"""
@dialyzer {:nowarn_function, diff: 2}
@spec diff(Description.t | Graph.t, Description.t | Graph.t) :: t
def diff(original_rdf_data, new_rdf_data)
def diff(%Description{} = description, description), do: new()
@ -143,6 +152,7 @@ defmodule RDF.Diff do
The diffs are merged by adding up the `additions` and `deletions` of both
diffs respectively.
"""
@spec merge(t, t) :: t
def merge(%__MODULE__{} = diff1, %__MODULE__{} = diff2) do
new(
additions: Graph.add(diff1.additions, diff2.additions),
@ -155,6 +165,7 @@ defmodule RDF.Diff do
A `RDF.Diff` is empty, if its `additions` and `deletions` graphs are empty.
"""
@spec empty?(t) :: boolean
def empty?(%__MODULE__{} = diff) do
Enum.empty?(diff.additions) and Enum.empty?(diff.deletions)
end
@ -168,6 +179,7 @@ defmodule RDF.Diff do
The result of an application is always a `RDF.Graph`, even if a `RDF.Description`
is given and the additions from the diff are all about the subject of this description.
"""
@spec apply(t, Description.t | Graph.t) :: Graph.t
def apply(diff, rdf_data)
def apply(%__MODULE__{} = diff, %Graph{} = graph) do

View file

@ -11,18 +11,33 @@ defmodule RDF.Graph do
"""
defstruct name: nil, descriptions: %{}, prefixes: nil, base_iri: nil
@behaviour Access
alias RDF.Description
import RDF.Statement
alias RDF.{Description, IRI, PrefixMap, Statement}
@type graph_description :: %{Statement.subject => Description.t}
@type t :: %__MODULE__{
name: IRI.t | nil,
descriptions: graph_description,
prefixes: PrefixMap.t | nil,
base_iri: IRI.t | nil
}
@type input :: Statement.t | Description.t | t
@type update_description_fun :: (Description.t -> Description.t)
@type get_and_update_description_fun :: (Description.t -> {Description.t, input} | :pop)
defstruct name: nil, descriptions: %{}, prefixes: nil, base_iri: nil
@type t :: module
@doc """
Creates an empty unnamed `RDF.Graph`.
"""
@spec new :: t
def new, do: %RDF.Graph{}
@doc """
@ -40,6 +55,7 @@ defmodule RDF.Graph do
RDF.Graph.new(name: EX.GraphName)
"""
@spec new(input | [input] | keyword) :: t
def new(data_or_options)
def new(data_or_options)
@ -82,6 +98,7 @@ defmodule RDF.Graph do
RDF.Graph.new({EX.S, EX.p, EX.O}, name: EX.GraphName, base_iri: EX.base)
"""
@spec new(input | [input], keyword) :: t
def new(data, options)
def new(%RDF.Graph{} = graph, options) do
@ -101,6 +118,12 @@ defmodule RDF.Graph do
See `new/2` for available arguments.
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object],
keyword
) :: t
def new(subject, predicate, objects, options \\ []),
do: new([], options) |> add(subject, predicate, objects)
@ -112,6 +135,7 @@ defmodule RDF.Graph do
another graph, as this function keeps graph name name, base IRI and default
prefixes as they are and just removes the triples.
"""
@spec clear(t) :: t
def clear(%RDF.Graph{} = graph) do
%RDF.Graph{graph | descriptions: %{}}
end
@ -120,6 +144,12 @@ defmodule RDF.Graph do
@doc """
Adds triples to a `RDF.Graph`.
"""
@spec add(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def add(%RDF.Graph{} = graph, subject, predicate, objects),
do: add(graph, {subject, predicate, objects})
@ -135,6 +165,7 @@ defmodule RDF.Graph do
prefixes of this graph will be added. In case of conflicting prefix mappings
the original prefix from `graph` will be kept.
"""
@spec add(t, input | [input]) :: t
def add(graph, triples)
def add(%RDF.Graph{} = graph, {subject, _, _} = statement),
@ -190,6 +221,7 @@ defmodule RDF.Graph do
RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
"""
@spec put(t, input | [input]) :: t
def put(graph, statements)
def put(%RDF.Graph{} = graph, {subject, _, _} = statement),
@ -227,6 +259,7 @@ defmodule RDF.Graph do
@doc """
Add statements to a `RDF.Graph`, overwriting all statements with the same subject and predicate.
"""
@spec put(t, Statement.coercible_subject, Description.statements | [Description.statements]) :: t
def put(graph, subject, predications)
def put(%RDF.Graph{descriptions: descriptions} = graph, subject, predications)
@ -267,6 +300,12 @@ defmodule RDF.Graph do
RDF.Graph.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
"""
@spec put(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def put(%RDF.Graph{} = graph, subject, predicate, objects),
do: put(graph, {subject, predicate, objects})
@ -274,6 +313,12 @@ defmodule RDF.Graph do
@doc """
Deletes statements from a `RDF.Graph`.
"""
@spec delete(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
) :: t
def delete(graph, subject, predicate, object),
do: delete(graph, {subject, predicate, object})
@ -286,6 +331,7 @@ defmodule RDF.Graph do
use `RDF.Data.delete/2`.
"""
@spec delete(t, input | [input]) :: t
def delete(graph, triples)
def delete(%RDF.Graph{} = graph, {subject, _, _} = triple),
@ -331,6 +377,10 @@ defmodule RDF.Graph do
@doc """
Deletes all statements with the given subjects.
"""
@spec delete_subjects(
t,
Statement.coercible_subject | [Statement.coercible_subject]
) :: t
def delete_subjects(graph, subjects)
def delete_subjects(%RDF.Graph{} = graph, subjects) when is_list(subjects) do
@ -376,6 +426,12 @@ defmodule RDF.Graph do
RDF.Graph.new([{EX.S, EX.p, EX.O}])
"""
@spec update(
t,
Statement.coercible_subject,
Description.statements | [Description.statements] | nil,
update_description_fun
) :: t
def update(graph = %RDF.Graph{}, subject, initial \\ nil, fun) do
subject = coerce_subject(subject)
@ -418,6 +474,7 @@ defmodule RDF.Graph do
"""
@impl Access
@spec fetch(t, Statement.coercible_subject) :: {:ok, Description.t} | :error
def fetch(%RDF.Graph{descriptions: descriptions}, subject) do
Access.fetch(descriptions, coerce_subject(subject))
end
@ -438,6 +495,7 @@ defmodule RDF.Graph do
:bar
"""
@spec get(t, Statement.coercible_subject, Description.t | nil) :: Description.t | nil
def get(%RDF.Graph{} = graph, subject, default \\ nil) do
case fetch(graph, subject) do
{:ok, value} -> value
@ -448,12 +506,14 @@ defmodule RDF.Graph do
@doc """
The `RDF.Description` of the given subject.
"""
@spec description(t, Statement.coercible_subject) :: Description.t | nil
def description(%RDF.Graph{descriptions: descriptions}, subject),
do: Map.get(descriptions, coerce_subject(subject))
@doc """
All `RDF.Description`s within a `RDF.Graph`.
"""
@spec descriptions(t) :: [Description.t]
def descriptions(%RDF.Graph{descriptions: descriptions}),
do: Map.values(descriptions)
@ -482,6 +542,8 @@ defmodule RDF.Graph do
"""
@impl Access
@spec get_and_update(t, Statement.coercible_subject, get_and_update_description_fun) ::
{Description.t, input}
def get_and_update(%RDF.Graph{} = graph, subject, fun) do
with subject = coerce_subject(subject) do
case fun.(get(graph, subject)) do
@ -499,6 +561,7 @@ defmodule RDF.Graph do
@doc """
Pops an arbitrary triple from a `RDF.Graph`.
"""
@spec pop(t) :: {Statement.t | nil, t}
def pop(graph)
def pop(%RDF.Graph{descriptions: descriptions} = graph)
@ -530,6 +593,7 @@ defmodule RDF.Graph do
"""
@impl Access
@spec pop(t, Statement.coercible_subject) :: {Description.t | nil, t}
def pop(%RDF.Graph{descriptions: descriptions} = graph, subject) do
case Access.pop(descriptions, coerce_subject(subject)) do
{nil, _} ->
@ -553,6 +617,7 @@ defmodule RDF.Graph do
2
"""
@spec subject_count(t) :: non_neg_integer
def subject_count(%RDF.Graph{descriptions: descriptions}),
do: Enum.count(descriptions)
@ -569,6 +634,7 @@ defmodule RDF.Graph do
3
"""
@spec triple_count(t) :: non_neg_integer
def triple_count(%RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, 0, fn ({_subject, description}, count) ->
count + Description.count(description)
@ -670,7 +736,8 @@ defmodule RDF.Graph do
{RDF.iri(EX.S1), RDF.iri(EX.p2), RDF.iri(EX.O3)},
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
"""
def triples(graph = %RDF.Graph{}), do: Enum.to_list(graph)
@spec triples(t) :: [Statement.t]
def triples(%RDF.Graph{} = graph), do: Enum.to_list(graph)
defdelegate statements(graph), to: RDF.Graph, as: :triples
@ -678,6 +745,7 @@ defmodule RDF.Graph do
@doc """
Checks if the given statement exists within a `RDF.Graph`.
"""
@spec include?(t, Statement.t) :: boolean
def include?(%RDF.Graph{descriptions: descriptions},
triple = {subject, _, _}) do
with subject = coerce_subject(subject),
@ -698,6 +766,7 @@ defmodule RDF.Graph do
iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.coercible_subject) :: boolean
def describes?(%RDF.Graph{descriptions: descriptions}, subject) do
with subject = coerce_subject(subject) do
Map.has_key?(descriptions, subject)
@ -747,6 +816,7 @@ defmodule RDF.Graph do
}
"""
@spec values(t, Statement.term_mapping) :: map
def values(graph, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Graph{descriptions: descriptions}, mapping) do
@ -764,6 +834,7 @@ defmodule RDF.Graph do
If `nil` is passed as the `subjects`, the subjects will not be limited.
"""
@spec take(t, [Statement.coercible_subject] | nil, [Statement.coercible_predicate] | nil) :: t
def take(graph, subjects, properties \\ nil)
def take(%RDF.Graph{} = graph, nil, nil), do: graph
@ -789,6 +860,7 @@ defmodule RDF.Graph do
Two `RDF.Graph`s are considered to be equal if they contain the same triples
and have the same name. The prefixes of the graph are irrelevant for equality.
"""
@spec equal?(t | any, t | any) :: boolean
def equal?(graph1, graph2)
def equal?(%RDF.Graph{} = graph1, %RDF.Graph{} = graph2) do
@ -808,6 +880,11 @@ defmodule RDF.Graph do
the new one. This behaviour can be customized by providing a `conflict_resolver`
function. See `RDF.PrefixMap.merge/3` for more on that.
"""
@spec add_prefixes(
t,
PrefixMap.t | map | keyword | nil,
PrefixMap.conflict_resolver | nil
) :: t
def add_prefixes(graph, prefixes, conflict_resolver \\ nil)
def add_prefixes(%RDF.Graph{} = graph, nil, _), do: graph
@ -832,6 +909,7 @@ defmodule RDF.Graph do
The `prefixes` can be a single prefix or a list of prefixes.
Prefixes not in prefixes of the graph are simply ignored.
"""
@spec delete_prefixes(t, PrefixMap.t) :: t
def delete_prefixes(graph, prefixes)
def delete_prefixes(%RDF.Graph{prefixes: nil} = graph, _), do: graph
@ -843,6 +921,7 @@ defmodule RDF.Graph do
@doc """
Clears all prefixes of the given `graph`.
"""
@spec clear_prefixes(t) :: t
def clear_prefixes(%RDF.Graph{} = graph) do
%RDF.Graph{graph | prefixes: nil}
end
@ -852,6 +931,7 @@ defmodule RDF.Graph do
The `base_iri` can be given as anything accepted by `RDF.IRI.coerce_base/1`.
"""
@spec set_base_iri(t, IRI.t | nil) :: t
def set_base_iri(graph, base_iri)
def set_base_iri(%RDF.Graph{} = graph, nil) do
@ -865,6 +945,7 @@ defmodule RDF.Graph do
@doc """
Clears the base IRI of the given `graph`.
"""
@spec clear_base_iri(t) :: t
def clear_base_iri(%RDF.Graph{} = graph) do
%RDF.Graph{graph | base_iri: nil}
end
@ -872,6 +953,7 @@ defmodule RDF.Graph do
@doc """
Clears the base IRI and all prefixes of the given `graph`.
"""
@spec clear_metadata(t) :: t
def clear_metadata(%RDF.Graph{} = graph) do
graph
|> clear_base_iri()

View file

@ -16,12 +16,16 @@ defmodule RDF.IRI do
see <https://tools.ietf.org/html/rfc3987>
"""
@enforce_keys [:value]
defstruct [:value]
alias RDF.Namespace
@type t :: module
@type t :: %__MODULE__{
value: String.t
}
@type coercible :: String.t | URI.t | module | t
@enforce_keys [:value]
defstruct [:value]
# see https://tools.ietf.org/html/rfc3986#appendix-B
@scheme_regex Regex.recompile!(~r/^([a-z][a-z0-9\+\-\.]*):/i)
@ -43,6 +47,7 @@ defmodule RDF.IRI do
@doc """
Creates a `RDF.IRI`.
"""
@spec new(coercible) :: t
def new(iri)
def new(iri) when is_binary(iri), do: %RDF.IRI{value: iri}
def new(qname) when is_atom(qname) and qname not in [nil, true, false],
@ -57,6 +62,7 @@ defmodule RDF.IRI do
see `valid?/1`
"""
@spec new!(coercible) :: t
def new!(iri)
def new!(iri) when is_binary(iri), do: iri |> valid!() |> new()
def new!(qname) when is_atom(qname) and qname not in [nil, true, false],
@ -71,6 +77,7 @@ defmodule RDF.IRI do
As opposed to `new/1` this also accepts bare `RDF.Vocabulary.Namespace` modules
and uses the base IRI from their definition.
"""
@spec coerce_base(coercible) :: t
def coerce_base(base_iri)
def coerce_base(module) when is_atom(module) do
@ -96,6 +103,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.valid!("not an iri")
** (RDF.IRI.InvalidError) Invalid IRI: "not an iri"
"""
@spec valid!(coercible) :: coercible
def valid!(iri) do
if not valid?(iri), do: raise RDF.IRI.InvalidError, "Invalid IRI: #{inspect iri}"
iri
@ -114,6 +122,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.valid?("not an iri")
false
"""
@spec valid?(coercible) :: boolean
def valid?(iri), do: absolute?(iri) # TODO: Provide a more elaborate validation
@ -123,6 +132,7 @@ defmodule RDF.IRI do
An absolute IRI is defined in [RFC3987](http://www.ietf.org/rfc/rfc3987.txt)
containing a scheme along with a path and optional query and fragment segments.
"""
@spec absolute?(any) :: boolean
def absolute?(iri)
def absolute?(value) when is_binary(value), do: not is_nil(scheme(value))
@ -149,6 +159,7 @@ defmodule RDF.IRI do
If the given is not an absolute IRI `nil` is returned.
"""
@spec absolute(coercible, coercible) :: t | nil
def absolute(iri, base) do
cond do
absolute?(iri) -> new(iri)
@ -164,6 +175,7 @@ defmodule RDF.IRI do
This function merges two IRIs as per
[RFC 3986, section 5.2](https://tools.ietf.org/html/rfc3986#section-5.2).
"""
@spec merge(coercible, coercible) :: t
def merge(base, rel) do
base
|> parse()
@ -184,6 +196,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.scheme("not an iri")
nil
"""
@spec scheme(coercible) :: String.t | nil
def scheme(iri)
def scheme(%RDF.IRI{value: value}), do: scheme(value)
def scheme(%URI{scheme: scheme}), do: scheme
@ -198,6 +211,7 @@ defmodule RDF.IRI do
@doc """
Parses an IRI into its components and returns them as an `URI` struct.
"""
@spec parse(coercible) :: URI.t
def parse(iri)
def parse(iri) when is_binary(iri), do: URI.parse(iri)
def parse(qname) when is_atom(qname) and qname not in [nil, true, false],
@ -213,6 +227,7 @@ defmodule RDF.IRI do
see <https://www.w3.org/TR/rdf-concepts/#section-Graph-URIref>
"""
@spec equal_value?(t | RDF.Literal.t, t | RDF.Literal.t) :: boolean | nil
def equal_value?(left, right)
def equal_value?(%RDF.IRI{value: left}, %RDF.IRI{value: right}),
@ -245,6 +260,7 @@ defmodule RDF.IRI do
"http://example.com/#Foo"
"""
@spec to_string(t | module) :: String.t
def to_string(iri)
def to_string(%RDF.IRI{value: value}),

View file

@ -7,15 +7,19 @@ defmodule RDF.List do
- <https://www.w3.org/TR/rdf11-mt/#rdf-collections>
"""
alias RDF.{BlankNode, Description, Graph, IRI}
@type t :: %__MODULE__{
head: IRI.t,
graph: Graph.t
}
@enforce_keys [:head]
defstruct [:head, :graph]
alias RDF.{Graph, Description, IRI, BlankNode}
@type t :: module
@rdf_nil RDF.nil
@doc """
Creates a `RDF.List` for a given RDF list node of a given `RDF.Graph`.
@ -27,6 +31,7 @@ defmodule RDF.List do
- does not contain cycles, i.e. `rdf:rest` statements don't refer to
preceding list nodes
"""
@spec new(IRI.coercible, Graph.t) :: t
def new(head, graph)
def new(head, graph) when is_atom(head) and head not in ~w[true false nil]a,
@ -66,6 +71,7 @@ defmodule RDF.List do
the head node of the empty list is always `RDF.nil`.
"""
@spec from(Enumerable.t, keyword) :: t
def from(list, opts \\ []) do
with head = Keyword.get(opts, :head, RDF.bnode),
graph = Keyword.get(opts, :graph, RDF.graph),
@ -114,6 +120,7 @@ defmodule RDF.List do
Nested lists are converted recursively.
"""
@spec values(t) :: Enumerable.t
def values(%RDF.List{graph: graph} = list) do
Enum.map list, fn node_description ->
value = Description.first(node_description, RDF.first)
@ -131,6 +138,7 @@ defmodule RDF.List do
@doc """
The RDF nodes constituting a `RDF.List` as an Elixir list.
"""
@spec nodes(t) :: [BlankNode.t]
def nodes(%RDF.List{} = list) do
Enum.map list, fn node_description -> node_description.subject end
end
@ -139,16 +147,18 @@ defmodule RDF.List do
@doc """
Checks if a list is the empty list.
"""
@spec empty?(t) :: boolean
def empty?(%RDF.List{head: @rdf_nil}), do: true
def empty?(_), do: false
def empty?(%RDF.List{}), do: false
@doc """
Checks if the given list consists of list nodes which are all blank nodes.
"""
@spec valid?(t) :: boolean
def valid?(%RDF.List{head: @rdf_nil}), do: true
def valid?(list) do
def valid?(%RDF.List{} = list) do
Enum.all? list, fn node_description ->
RDF.bnode?(node_description.subject)
end
@ -164,6 +174,7 @@ defmodule RDF.List do
Note: This function doesn't indicate if the list is valid.
See `new/2` and `valid?/2` for validations.
"""
@spec node?(any, Graph.t) :: boolean
def node?(list_node, graph)
def node?(@rdf_nil, _),

View file

@ -3,11 +3,27 @@ defmodule RDF.Literal do
RDF literals are leaf nodes of a RDF graph containing raw data, like strings and numbers.
"""
defstruct [:value, :uncanonical_lexical, :datatype, :language]
@type t :: module
alias RDF.Datatype.NS.XSD
alias RDF.IRI
@type literal_value ::
RDF.Boolean.value
| RDF.Integer.value
| RDF.Double.value
| RDF.String.value
| RDF.Decimal.value
| RDF.Date.value
| RDF.Time.value
| RDF.DateTime.value
@type t :: %__MODULE__{
value: literal_value,
datatype: IRI.t,
uncanonical_lexical: String.t | nil,
language: String.t | nil
}
defstruct [:value, :datatype, :uncanonical_lexical, :language]
# to be able to pattern-match on plain types; we can't use RDF.Literal.Guards here since these aren't compiled here yet
@xsd_string XSD.string
@ -40,6 +56,7 @@ defmodule RDF.Literal do
%RDF.Literal{value: 42, datatype: XSD.integer}
"""
@spec new(literal_value | t) :: t
def new(value)
def new(%RDF.Literal{} = literal), do: literal
@ -63,6 +80,7 @@ defmodule RDF.Literal do
@doc """
Creates a new `RDF.Literal` with the given datatype or language tag.
"""
@spec new(literal_value | t, map | keyword) :: t
def new(value, opts)
def new(value, opts) when is_list(opts),
@ -118,6 +136,7 @@ defmodule RDF.Literal do
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{value: "foo", datatype: ~I<http://www.w3.org/1999/02/22-rdf-syntax-ns#langString>, language: nil}
"""
@spec new!(literal_value | t, map | keyword) :: t
def new!(value, opts \\ %{}) do
with %RDF.Literal{} = literal <- new(value, opts) do
if valid?(literal) do
@ -134,6 +153,7 @@ defmodule RDF.Literal do
@doc """
Returns the lexical representation of the given literal according to its datatype.
"""
@spec lexical(t) :: String.t
def lexical(%RDF.Literal{value: value, uncanonical_lexical: nil, datatype: id} = literal) do
case RDF.Datatype.get(id) do
nil -> to_string(value)
@ -146,6 +166,7 @@ defmodule RDF.Literal do
@doc """
Returns the given literal in its canonical lexical representation.
"""
@spec canonical(t) :: t
def canonical(%RDF.Literal{uncanonical_lexical: nil} = literal), do: literal
def canonical(%RDF.Literal{datatype: id} = literal) do
case RDF.Datatype.get(id) do
@ -158,13 +179,15 @@ defmodule RDF.Literal do
@doc """
Returns if the given literal is in its canonical lexical representation.
"""
@spec canonical?(t) :: boolean
def canonical?(%RDF.Literal{uncanonical_lexical: nil}), do: true
def canonical?(_), do: false
def canonical?(%RDF.Literal{} = _), do: false
@doc """
Returns if the value of the given literal is a valid according to its datatype.
"""
@spec valid?(t) :: boolean
def valid?(%RDF.Literal{datatype: id} = literal) do
case RDF.Datatype.get(id) do
nil -> true
@ -180,8 +203,9 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
"""
@spec simple?(t) :: boolean
def simple?(%RDF.Literal{datatype: @xsd_string}), do: true
def simple?(_), do: false
def simple?(%RDF.Literal{} = _), do: false
@doc """
@ -189,8 +213,9 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
"""
@spec has_language?(t) :: boolean
def has_language?(%RDF.Literal{datatype: @lang_string}), do: true
def has_language?(_), do: false
def has_language?(%RDF.Literal{} = _), do: false
@doc """
@ -200,6 +225,7 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/rdf-concepts/#dfn-typed-literal>
"""
@spec has_datatype?(t) :: boolean
def has_datatype?(literal) do
not plain?(literal) and not has_language?(literal)
end
@ -213,10 +239,12 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
"""
@spec plain?(t) :: boolean
def plain?(%RDF.Literal{datatype: datatype})
when datatype in @plain_types, do: true
def plain?(_), do: false
def plain?(%RDF.Literal{} = _), do: false
@spec typed?(t) :: boolean
def typed?(literal), do: not plain?(literal)
@ -229,6 +257,7 @@ defmodule RDF.Literal do
see <https://www.w3.org/TR/rdf-concepts/#section-Literal-Equality>
"""
@spec equal_value?(t | IRI.t | any, t | IRI.t | any) :: boolean | nil
def equal_value?(left, right)
def equal_value?(%RDF.Literal{datatype: id1} = literal1, %RDF.Literal{datatype: id2} = literal2) do
@ -266,6 +295,7 @@ defmodule RDF.Literal do
Returns `nil` when the given arguments are not comparable datatypes.
"""
@spec less_than?(t | any, t | any) :: boolean | nil
def less_than?(literal1, literal2) do
case compare(literal1, literal2) do
:lt -> true
@ -280,6 +310,7 @@ defmodule RDF.Literal do
Returns `nil` when the given arguments are not comparable datatypes.
"""
@spec greater_than?(t | any, t | any) :: boolean | nil
def greater_than?(literal1, literal2) do
case compare(literal1, literal2) do
:gt -> true
@ -300,6 +331,7 @@ defmodule RDF.Literal do
Returns `nil` when the given arguments are not comparable datatypes.
"""
@spec compare(t | any, t | any) :: :eq | :lt | :gt | nil
def compare(left, right)
def compare(%RDF.Literal{datatype: id1} = literal1, %RDF.Literal{datatype: id2} = literal2) do
@ -331,6 +363,7 @@ defmodule RDF.Literal do
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@spec matches?(t | String.t, t | String.t, t | String.t) :: boolean
def matches?(value, pattern, flags \\ "") do
string = to_string(value)
case xpath_pattern(pattern, flags) do
@ -351,6 +384,8 @@ defmodule RDF.Literal do
end
@doc false
@spec xpath_pattern(t | String.t, t | String.t) ::
{:q | :qi, String.t} | {:regex, Regex.t} | {:error, any}
def xpath_pattern(pattern, flags)
def xpath_pattern(%RDF.Literal{datatype: @xsd_string} = pattern, flags),
@ -379,6 +414,7 @@ defmodule RDF.Literal do
end
@doc false
@spec convert_utf_escaping(String.t) :: String.t
def convert_utf_escaping(string) do
require Integer

View file

@ -7,10 +7,12 @@ defmodule RDF.Namespace do
namespaces for JSON-LD contexts.
"""
alias RDF.IRI
@doc """
Resolves a term to a `RDF.IRI`.
"""
@callback __resolve_term__(atom) :: RDF.IRI.t
@callback __resolve_term__(atom) :: IRI.t
@doc """
All terms of a `RDF.Namespace`.
@ -25,9 +27,10 @@ defmodule RDF.Namespace do
delegates to remaining part of the term to `__resolve_term__/1` of this
determined namespace.
"""
@spec resolve_term(IRI.t | module) :: IRI.t
def resolve_term(expr)
def resolve_term(%RDF.IRI{} = iri), do: iri
def resolve_term(%IRI{} = iri), do: iri
def resolve_term(namespaced_term) when is_atom(namespaced_term) do
namespaced_term

View file

@ -5,14 +5,31 @@ defmodule RDF.PrefixMap do
`RDF.PrefixMap` implements the `Enumerable` protocol.
"""
alias RDF.IRI
@type prefix :: atom
@type namespace :: IRI.t
@type coercible_prefix :: atom | String.t
@type coercible_namespace :: atom | String.t | IRI.t
@type prefix_map :: %{prefix => namespace}
@type conflict_resolver ::
(coercible_prefix, coercible_namespace, coercible_namespace -> coercible_namespace)
@type t :: %__MODULE__{
map: prefix_map
}
defstruct map: %{}
alias RDF.IRI
@doc """
Creates an empty `RDF.PrefixMap`.
"""
def new(), do: %__MODULE__{}
@spec new :: t
def new, do: %__MODULE__{}
@doc """
Creates a new `RDF.PrefixMap`.
@ -21,6 +38,7 @@ defmodule RDF.PrefixMap do
The keys for the prefixes can be given as atoms or strings and will be normalized to atoms.
The namespaces can be given as `RDF.IRI`s or strings and will be normalized to `RDF.IRI`s.
"""
@spec new(t | map | keyword) :: t
def new(map)
def new(%__MODULE__{} = prefix_map), do: prefix_map
@ -49,6 +67,7 @@ defmodule RDF.PrefixMap do
Unless a mapping of the given prefix to a different namespace already exists,
an ok tuple is returned, other an error tuple.
"""
@spec add(t, coercible_prefix, coercible_namespace) :: {:ok, t} | {:error, String.t}
def add(prefix_map, prefix, namespace)
def add(%__MODULE__{map: map}, prefix, %IRI{} = namespace) when is_atom(prefix) do
@ -68,6 +87,7 @@ defmodule RDF.PrefixMap do
@doc """
Adds a prefix mapping to the given `RDF.PrefixMap` and raises an exception in error cases.
"""
@spec add!(t, coercible_prefix, coercible_namespace) :: t
def add!(prefix_map, prefix, namespace) do
with {:ok, new_prefix_map} <- add(prefix_map, prefix, namespace) do
new_prefix_map
@ -89,6 +109,7 @@ defmodule RDF.PrefixMap do
See also `merge/3` which allows you to resolve conflicts with a function.
"""
@spec merge(t, t | map | keyword) :: {:ok, t} | {:error, [atom | String.t]}
def merge(prefix_map1, prefix_map2)
def merge(%__MODULE__{map: map1}, %__MODULE__{map: map2}) do
@ -128,6 +149,7 @@ defmodule RDF.PrefixMap do
If everything could be merged, an `:ok` tuple is returned.
"""
@spec merge(t, t | map | keyword, conflict_resolver | nil) :: {:ok, t} | {:error, [atom | String.t]}
def merge(prefix_map1, prefix_map2, conflict_resolver)
def merge(%__MODULE__{map: map1}, %__MODULE__{map: map2}, conflict_resolver)
@ -180,20 +202,22 @@ defmodule RDF.PrefixMap do
See `merge/2` and `merge/3` for more information on merging prefix maps.
"""
@spec merge!(t, t | map | keyword, conflict_resolver | nil) :: t
def merge!(prefix_map1, prefix_map2, conflict_resolver \\ nil) do
with {:ok, new_prefix_map} <- merge(prefix_map1, prefix_map2, conflict_resolver) do
new_prefix_map
else
{:error, conflicts} ->
raise "conflicting prefix mappings: #{
conflicts |> Stream.map(&inspect/1) |> Enum.join(", ")
}"
conflicts = conflicts |> Stream.map(&inspect/1) |> Enum.join(", ")
raise "conflicting prefix mappings: #{conflicts}"
end
end
@doc """
Deletes a prefix mapping from the given `RDF.PrefixMap`.
"""
@spec delete(t, coercible_prefix) :: t
def delete(prefix_map, prefix)
def delete(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -209,6 +233,7 @@ defmodule RDF.PrefixMap do
If `prefixes` contains prefixes that are not in `prefix_map`, they're simply ignored.
"""
@spec drop(t, [coercible_prefix]) :: t
def drop(prefix_map, prefixes)
def drop(%__MODULE__{map: map}, prefixes) do
@ -229,6 +254,7 @@ defmodule RDF.PrefixMap do
Returns `nil`, when the given `prefix` is not present in `prefix_map`.
"""
@spec namespace(t, coercible_prefix) :: namespace | nil
def namespace(prefix_map, prefix)
def namespace(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -244,6 +270,7 @@ defmodule RDF.PrefixMap do
Returns `nil`, when the given `namespace` is not present in `prefix_map`.
"""
@spec prefix(t, coercible_namespace) :: coercible_prefix | nil
def prefix(prefix_map, namespace)
def prefix(%__MODULE__{map: map}, %IRI{} = namespace) do
@ -257,6 +284,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns whether the given prefix exists in the given `RDF.PrefixMap`.
"""
@spec has_prefix?(t, coercible_prefix) :: boolean
def has_prefix?(prefix_map, prefix)
def has_prefix?(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -270,6 +298,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns all prefixes from the given `RDF.PrefixMap`.
"""
@spec prefixes(t) :: [coercible_prefix]
def prefixes(%__MODULE__{map: map}) do
Map.keys(map)
end
@ -277,6 +306,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns all namespaces from the given `RDF.PrefixMap`.
"""
@spec namespaces(t) :: [coercible_namespace]
def namespaces(%__MODULE__{map: map}) do
Map.values(map)
end

View file

@ -6,7 +6,16 @@ defmodule RDF.Quad do
RDF values for subject, predicate, object and a graph context.
"""
alias RDF.Statement
alias RDF.{Literal, Statement}
@type t :: {Statement.subject, Statement.predicate, Statement.object, Statement.graph_name}
@type coercible_t ::
{Statement.coercible_subject, Statement.coercible_predicate,
Statement.coercible_object, Statement.coercible_graph_name}
@type t_values :: {String.t, String.t, Literal.literal_value, String.t}
@doc """
Creates a `RDF.Quad` with proper RDF values.
@ -22,6 +31,12 @@ defmodule RDF.Quad do
iex> RDF.Quad.new(EX.S, EX.p, 42, EX.Graph)
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object,
Statement.coercible_graph_name
) :: t
def new(subject, predicate, object, graph_context) do
{
Statement.coerce_subject(subject),
@ -45,6 +60,7 @@ defmodule RDF.Quad do
iex> RDF.Quad.new {EX.S, EX.p, 42, EX.Graph}
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
"""
@spec new(coercible_t) :: t
def new({subject, predicate, object, graph_context}),
do: new(subject, predicate, object, graph_context)
@ -78,6 +94,7 @@ defmodule RDF.Quad do
{:S, :p, 42, ~I<http://example.com/Graph>}
"""
@spec values(t | any, Statement.term_mapping) :: t_values | nil
def values(quad, mapping \\ &Statement.default_term_mapping/1)
def values({subject, predicate, object, graph_context}, mapping) do
@ -102,6 +119,7 @@ defmodule RDF.Quad do
position only IRIs and blank nodes allowed, while on the predicate and graph
context position only IRIs allowed. The object position can be any RDF term.
"""
@spec valid?(t | any) :: boolean
def valid?(tuple)
def valid?({_, _, _, _} = quad), do: Statement.valid?(quad)
def valid?(_), do: false

View file

@ -11,7 +11,8 @@ defmodule RDF.Serialization.Encoder do
It returns an `{:ok, string}` tuple, with `string` being the serialized
`RDF.Graph` or `RDF.Dataset`, or `{:error, reason}` if an error occurs.
"""
@callback encode(RDF.Graph.t | RDF.Dataset.t, keyword) :: keyword(String.t)
@callback encode(RDF.Graph.t | RDF.Dataset.t, keyword) ::
{:ok, String.t} | {:error, any}
@doc """
Encodes a `RDF.Graph` or `RDF.Dataset`.

View file

@ -175,6 +175,7 @@ defmodule RDF.Turtle.Encoder do
|> Enum.join(" ;" <> newline_indent(nesting))
end
@dialyzer {:nowarn_function, order_predications: 1}
defp order_predications(predications) do
sorted_predications =
@predicate_order
@ -223,6 +224,7 @@ defmodule RDF.Turtle.Encoder do
end
end
@dialyzer {:nowarn_function, list_subject_description: 1}
defp list_subject_description(description) do
with description = Description.delete_predicates(description, [RDF.first, RDF.rest]) do
if Enum.count(description.predications) == 0 do

View file

@ -82,6 +82,7 @@ defmodule RDF.Turtle.Encoder.State do
@list_properties MapSet.new([RDF.first, RDF.rest])
@dialyzer {:nowarn_function, to_list?: 2}
defp to_list?(%Description{} = description, 1) do
Description.count(description) == 2 and
Description.predicates(description) |> MapSet.equal?(@list_properties)

View file

@ -5,18 +5,23 @@ defmodule RDF.Statement do
A RDF statement is either a `RDF.Triple` or a `RDF.Quad`.
"""
alias RDF.{Triple, Quad, IRI, BlankNode, Literal}
alias RDF.{BlankNode, IRI, Literal, Quad, Term, Triple}
@type subject :: IRI.t | BlankNode.t
@type predicate :: IRI.t
@type predicate :: IRI.t | BlankNode.t
@type object :: IRI.t | BlankNode.t | Literal.t
@type graph_name :: IRI.t | BlankNode.t
@type coercible_subject :: subject | atom | String.t
@type coercible_predicate :: predicate | atom | String.t
@type coercible_object :: object | atom | String.t # TODO: all basic Elixir types coercible to Literals
@type coercible_object :: object | Literal.literal_value
@type coercible_graph_name :: graph_name | atom | String.t
@type qualified_term :: {atom, Term.t | nil}
@type term_mapping :: (qualified_term -> Literal.literal_value | nil)
@type t :: Triple.t | Quad.t
@doc """
Creates a `RDF.Statement` tuple with proper RDF values.
@ -30,11 +35,14 @@ defmodule RDF.Statement do
iex> RDF.Statement.coerce {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
"""
@spec coerce(Triple.coercible_t) :: Triple.t
@spec coerce(Quad.coercible_t) :: Quad.t
def coerce(statement)
def coerce({_, _, _} = triple), do: Triple.new(triple)
def coerce({_, _, _, _} = quad), do: Quad.new(quad)
@doc false
@spec coerce_subject(coercible_subject) :: subject
def coerce_subject(iri)
def coerce_subject(iri = %IRI{}), do: iri
def coerce_subject(bnode = %BlankNode{}), do: bnode
@ -43,6 +51,7 @@ defmodule RDF.Statement do
def coerce_subject(arg), do: raise RDF.Triple.InvalidSubjectError, subject: arg
@doc false
@spec coerce_predicate(coercible_predicate) :: predicate
def coerce_predicate(iri)
def coerce_predicate(iri = %IRI{}), do: iri
# Note: Although, RDF does not allow blank nodes for properties, JSON-LD allows
@ -53,6 +62,7 @@ defmodule RDF.Statement do
def coerce_predicate(arg), do: raise RDF.Triple.InvalidPredicateError, predicate: arg
@doc false
@spec coerce_object(coercible_object) :: object
def coerce_object(iri)
def coerce_object(iri = %IRI{}), do: iri
def coerce_object(literal = %Literal{}), do: literal
@ -62,6 +72,7 @@ defmodule RDF.Statement do
def coerce_object(arg), do: Literal.new(arg)
@doc false
@spec coerce_graph_name(coercible_graph_name) :: graph_name
def coerce_graph_name(iri)
def coerce_graph_name(nil), do: nil
def coerce_graph_name(iri = %IRI{}), do: iri
@ -105,12 +116,14 @@ defmodule RDF.Statement do
{"S", :p, 42, ~I<http://example.com/Graph>}
"""
@spec values(t | any, term_mapping) :: Triple.t_values | Quad.t_values | nil
def values(statement, mapping \\ &default_term_mapping/1)
def values({_, _, _} = triple, mapping), do: RDF.Triple.values(triple, mapping)
def values({_, _, _, _} = quad, mapping), do: RDF.Quad.values(quad, mapping)
def values(_, _), do: nil
@doc false
@spec default_term_mapping(qualified_term) :: Literal.literal_value | nil
def default_term_mapping(qualified_term)
def default_term_mapping({:graph_name, nil}), do: nil
def default_term_mapping({_, term}), do: RDF.Term.value(term)
@ -123,6 +136,7 @@ defmodule RDF.Statement do
position only IRIs and blank nodes allowed, while on the predicate and graph
context position only IRIs allowed. The object position can be any RDF term.
"""
@spec valid?(Triple.t | Quad.t | any) :: boolean
def valid?(tuple)
def valid?({subject, predicate, object}) do
@ -136,18 +150,22 @@ defmodule RDF.Statement do
def valid?(_), do: false
@spec valid_subject?(subject | any) :: boolean
def valid_subject?(%IRI{}), do: true
def valid_subject?(%BlankNode{}), do: true
def valid_subject?(_), do: false
@spec valid_predicate?(predicate | any) :: boolean
def valid_predicate?(%IRI{}), do: true
def valid_predicate?(_), do: false
@spec valid_object?(object | any) :: boolean
def valid_object?(%IRI{}), do: true
def valid_object?(%BlankNode{}), do: true
def valid_object?(%Literal{}), do: true
def valid_object?(_), do: false
@spec valid_graph_name?(graph_name | any) :: boolean
def valid_graph_name?(%IRI{}), do: true
def valid_graph_name?(_), do: false

View file

@ -11,7 +11,6 @@ defprotocol RDF.Term do
see <https://www.w3.org/TR/sparql11-query/#defn_RDFTerm>
"""
@type t :: RDF.IRI.t | RDF.BlankNode.t | RDF.Literal.t
@ -110,6 +109,8 @@ defimpl RDF.Term, for: RDF.BlankNode do
end
defimpl RDF.Term, for: Reference do
@dialyzer {:nowarn_function, equal_value?: 2}
@dialyzer {:nowarn_function, coerce: 1}
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.BlankNode.new(term)

View file

@ -6,7 +6,16 @@ defmodule RDF.Triple do
RDF values for subject, predicate and object.
"""
alias RDF.Statement
alias RDF.{Literal, Statement}
@type t :: {Statement.subject, Statement.predicate, Statement.object}
@type coercible_t ::
{Statement.coercible_subject, Statement.coercible_predicate,
Statement.coercible_object}
@type t_values :: {String.t, String.t, Literal.literal_value}
@doc """
Creates a `RDF.Triple` with proper RDF values.
@ -22,6 +31,11 @@ defmodule RDF.Triple do
iex> RDF.Triple.new(EX.S, EX.p, 42)
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object
) :: t
def new(subject, predicate, object) do
{
Statement.coerce_subject(subject),
@ -44,6 +58,7 @@ defmodule RDF.Triple do
iex> RDF.Triple.new {EX.S, EX.p, 42}
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
"""
@spec new(coercible_t) :: t
def new({subject, predicate, object}), do: new(subject, predicate, object)
@ -72,6 +87,7 @@ defmodule RDF.Triple do
{"S", "p", 42}
"""
@spec values(t | any, Statement.term_mapping) :: t_values | nil
def values(triple, mapping \\ &Statement.default_term_mapping/1)
def values({subject, predicate, object}, mapping) do
@ -95,6 +111,7 @@ defmodule RDF.Triple do
position only IRIs and blank nodes allowed, while on the predicate position
only IRIs allowed. The object position can be any RDF term.
"""
@spec valid?(t | any) :: boolean
def valid?(tuple)
def valid?({_, _, _} = triple), do: Statement.valid?(triple)
def valid?(_), do: false

View file

@ -53,6 +53,7 @@ defmodule RDF.Utils.ResourceClassifier do
|> Enum.map(&RDF.iri/1)
|> MapSet.new
@dialyzer {:nowarn_function, property_by_rdf_type?: 1}
defp property_by_rdf_type?(nil), do: nil
defp property_by_rdf_type?(types) do
not (

View file

@ -295,6 +295,7 @@ defmodule RDF.Vocabulary.Namespace do
terms
end
@dialyzer {:nowarn_function, valid_term?: 1}
defp valid_term?(term) do
not MapSet.member?(@invalid_terms, term)
end
@ -567,12 +568,14 @@ defmodule RDF.Vocabulary.Namespace do
defp vocab_term?(_), do: false
@doc false
@spec term_to_iri(String.t, String.t | atom) :: RDF.IRI.t
def term_to_iri(base_iri, term) when is_atom(term),
do: term_to_iri(base_iri, Atom.to_string(term))
def term_to_iri(base_iri, term),
do: RDF.iri(base_iri <> term)
@doc false
@spec vocabulary_namespace?(module) :: boolean
def vocabulary_namespace?(name) do
Code.ensure_compiled?(name) && function_exported?(name, :__base_iri__, 0)
end

13
mix.exs
View file

@ -15,6 +15,9 @@ defmodule RDF.Mixfile do
deps: deps(),
elixirc_paths: elixirc_paths(Mix.env()),
# Dialyzer
dialyzer: dialyzer(),
# Hex
package: package(),
description: description(),
@ -77,6 +80,16 @@ defmodule RDF.Mixfile do
]
end
defp dialyzer do
# Dialyzer will emit a warning when the name of the plt file is set
# as people misused it in the past. Without setting a name caching of
# this file is much more trickier, so we still use this functionality.
[
plt_add_apps: [:mix],
ignore_warnings: ".dialyzer_ignore"
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
end

View file

@ -1,26 +1,26 @@
%{
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "1.1.4", "c2f3b73c895d81d859cec7fcee7ffdb972c595fd8e85ab6f8c2adbf01cf7c29c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.0", "397e750b879df18198afc66505ca87ecf6a96645545585899f6185178433cc09", [:mix], [], "hexpm"},
"erlang_term": {:hex, :erlang_term, "1.7.5", "0ac88c935c14798e0253530c0ecaab9dd81e084add4d81c5f8ade996c1df5bb9", [:rebar3], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.11.2", "0c6f2c8db7683b0caa9d490fb8125709c54580b4255ffa7ad35f3264b075a643", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"inch_ex": {:hex, :inch_ex, "1.0.1", "1f0af1a83cec8e56f6fc91738a09c838e858db3d78ef5f2ec040fe4d5a62dabf", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"credo": {:hex, :credo, "1.1.4", "c2f3b73c895d81d859cec7fcee7ffdb972c595fd8e85ab6f8c2adbf01cf7c29c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6e25e83b4f460de47f33743239ac3d8d3f0f28ee467b0392e47c040a66cd3377"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm", "52694ef56e60108e5012f8af9673874c66ed58ac1c4fae9b5b7ded31786663f5"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"},
"earmark": {:hex, :earmark, "1.4.0", "397e750b879df18198afc66505ca87ecf6a96645545585899f6185178433cc09", [:mix], [], "hexpm", "4bedcec35de03b5f559fd2386be24d08f7637c374d3a85d3fe0911eecdae838a"},
"erlang_term": {:hex, :erlang_term, "1.7.5", "0ac88c935c14798e0253530c0ecaab9dd81e084add4d81c5f8ade996c1df5bb9", [:rebar3], [], "hexpm", "3684a557ff3be76e5847a0b37ecafd16cca7a9f8c77647d8e8e014941277374e"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"},
"excoveralls": {:hex, :excoveralls, "0.11.2", "0c6f2c8db7683b0caa9d490fb8125709c54580b4255ffa7ad35f3264b075a643", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e11a4490976aabeed3eb9dc70ec94a4f2d11fed5c9d4b5dc5d89bfa0a215abb5"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "c2790c9f0f7205f4a362512192dee8179097394400e745e4d20bab7226a8eaad"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"inch_ex": {:hex, :inch_ex, "1.0.1", "1f0af1a83cec8e56f6fc91738a09c838e858db3d78ef5f2ec040fe4d5a62dabf", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "08fd8a9205d3e1aefad9d7cb2a7f6b346e4a3e6ff09e139f6ec978f3a479ba14"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm", "603561dc0fd62f4f2ea9b890f4e20e1a0d388746d6e20557cafb1b16950de88c"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
}