Add function signatures

This commit is contained in:
rustra 2020-03-02 02:07:31 +01:00
parent 62fc1a753b
commit 8345e101d3
22 changed files with 363 additions and 60 deletions

View file

@ -7,7 +7,7 @@ defmodule RDF.BlankNode do
"""
@type t :: %__MODULE__{
id: binary
id: String.t
}
@enforce_keys [:id]
@ -16,6 +16,7 @@ defmodule RDF.BlankNode do
@doc """
Creates a `RDF.BlankNode` with an arbitrary internal id.
"""
@spec new :: t
def new,
do: new(make_ref())
@ -27,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),
@ -44,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()) :: map
@callback init(opts :: map | keyword) :: map
@doc """
Generates a blank node.

View file

@ -15,24 +15,28 @@ defmodule RDF.Dataset do
@behaviour Access
alias RDF.{Graph, Description}
alias RDF.{Description, Graph, IRI, Statement}
import RDF.Statement
@type graphs_key :: RDF.IRI.t | nil
@type graphs_key :: IRI.t | nil
@type t :: %__MODULE__{
name: RDF.IRI.t | nil,
graphs: %{graphs_key => RDF.Graph.t}
name: IRI.t | nil,
graphs: %{graphs_key => Graph.t}
}
@type t_param :: Graph.t_param | t
@type update_graph_fun :: (Graph.t -> {Graph.t, t_param} | :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`.
@ -49,6 +53,7 @@ defmodule RDF.Dataset do
RDF.Graph.new(name: EX.GraphName)
"""
@spec new(t_param | [t_param] | keyword) :: t
def new(data_or_options)
def new(data_or_options)
@ -78,6 +83,7 @@ defmodule RDF.Dataset do
- `name`: the name of the dataset to be created
"""
@spec new(t_param | [t_param], keyword) :: t
def new(data, options)
def new(%RDF.Dataset{} = graph, options) do
@ -98,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, t_param | [t_param], boolean | nil) :: t
def add(dataset, statements, graph_context \\ false)
def add(dataset, statements, graph_context) when is_list(statements) do
@ -179,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, t_param | [t_param], Statement.coercible_graph_name | boolean | nil) :: t
def put(dataset, statements, graph_context \\ false)
def put(%RDF.Dataset{} = dataset, {subject, predicate, objects}, false),
@ -299,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, t_param | [t_param], 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
@ -362,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
@ -379,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)
@ -399,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
@ -421,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
@ -431,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)
@ -444,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)
@ -470,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, t_param}
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
@ -487,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)
@ -520,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, _} ->
@ -543,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)
@ -641,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)
@ -665,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
@ -691,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
@ -713,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
@ -775,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
@ -790,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

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

@ -13,9 +13,17 @@ defmodule RDF.Description do
@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
@ -30,21 +38,22 @@ defmodule RDF.Description do
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),
@ -57,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)
@ -73,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
@ -101,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}),
@ -145,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},
@ -176,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}),
@ -224,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
@ -261,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}),
@ -297,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
@ -328,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)}
@ -348,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
@ -367,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, [])
@ -395,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)
@ -444,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
@ -458,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})
@ -511,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
@ -530,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
@ -560,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
@ -569,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
@ -577,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
@ -586,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},
@ -617,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
@ -656,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
@ -674,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
@ -688,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

@ -9,8 +9,8 @@ defmodule RDF.Diff do
alias RDF.{Description, Graph}
@type t :: %__MODULE__{
additions: RDF.Graph.t,
deletions: RDF.Graph.t
additions: Graph.t,
deletions: Graph.t
}
defstruct [:additions, :deletions]
@ -23,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(),
@ -58,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()
@ -149,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),
@ -161,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
@ -174,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

@ -13,22 +13,31 @@ defmodule RDF.Graph do
@behaviour Access
alias RDF.Description
import RDF.Statement
alias RDF.{Description, IRI, PrefixMap, Statement}
@type graph_description :: %{IRI.t => Description.t}
@type t :: %__MODULE__{
name: RDF.IRI.t | nil,
name: IRI.t | nil,
descriptions: graph_description,
prefixes: RDF.PrefixMap.t | nil,
base_iri: RDF.IRI | nil
prefixes: PrefixMap.t | nil,
base_iri: IRI.t | nil
}
@type t_param :: Statement.t | Description.t | t
@type update_description_fun :: (Description.t -> Description.t)
@type get_and_update_description_fun :: (Description.t -> {Description.t, t_param} | :pop)
defstruct name: nil, descriptions: %{}, prefixes: nil, base_iri: nil
@doc """
Creates an empty unnamed `RDF.Graph`.
"""
@spec new :: t
def new, do: %RDF.Graph{}
@doc """
@ -46,6 +55,7 @@ defmodule RDF.Graph do
RDF.Graph.new(name: EX.GraphName)
"""
@spec new(t_param | [t_param] | keyword) :: t
def new(data_or_options)
def new(data_or_options)
@ -88,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(t_param | [t_param], keyword) :: t
def new(data, options)
def new(%RDF.Graph{} = graph, options) do
@ -107,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)
@ -118,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
@ -126,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})
@ -141,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, t_param | [t_param]) :: t
def add(graph, triples)
def add(%RDF.Graph{} = graph, {subject, _, _} = statement),
@ -196,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, t_param | [t_param]) :: t
def put(graph, statements)
def put(%RDF.Graph{} = graph, {subject, _, _} = statement),
@ -233,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)
@ -273,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})
@ -280,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})
@ -292,6 +331,7 @@ defmodule RDF.Graph do
use `RDF.Data.delete/2`.
"""
@spec delete(t, t_param | [t_param]) :: t
def delete(graph, triples)
def delete(%RDF.Graph{} = graph, {subject, _, _} = triple),
@ -337,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
@ -382,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)
@ -424,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
@ -444,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
@ -454,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)
@ -488,6 +542,8 @@ defmodule RDF.Graph do
"""
@impl Access
@spec get_and_update(t, Statement.coercible_subject, get_and_update_description_fun) ::
{Description.t, t_param}
def get_and_update(%RDF.Graph{} = graph, subject, fun) do
with subject = coerce_subject(subject) do
case fun.(get(graph, subject)) do
@ -505,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)
@ -536,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, _} ->
@ -559,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)
@ -575,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)
@ -676,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
@ -684,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),
@ -704,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)
@ -753,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
@ -770,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
@ -795,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
@ -814,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
@ -838,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
@ -849,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
@ -858,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
@ -871,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
@ -878,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,14 @@ defmodule RDF.IRI do
see <https://tools.ietf.org/html/rfc3987>
"""
alias RDF.Namespace
alias RDF.{Datatype, Namespace}
@type t :: %__MODULE__{
value: binary
value: String.t
}
@type t_param :: String.t | Datatype.t | URI.t | t
@enforce_keys [:value]
defstruct [:value]
@ -45,6 +47,7 @@ defmodule RDF.IRI do
@doc """
Creates a `RDF.IRI`.
"""
@spec new(t_param) :: 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],
@ -59,6 +62,7 @@ defmodule RDF.IRI do
see `valid?/1`
"""
@spec new!(t_param) :: 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],
@ -73,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(t_param) :: t
def coerce_base(base_iri)
def coerce_base(module) when is_atom(module) do
@ -98,6 +103,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.valid!("not an iri")
** (RDF.IRI.InvalidError) Invalid IRI: "not an iri"
"""
@spec valid!(t_param) :: t_param
def valid!(iri) do
if not valid?(iri), do: raise RDF.IRI.InvalidError, "Invalid IRI: #{inspect iri}"
iri
@ -116,6 +122,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.valid?("not an iri")
false
"""
@spec valid?(t_param) :: boolean
def valid?(iri), do: absolute?(iri) # TODO: Provide a more elaborate validation
@ -125,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))
@ -151,6 +159,7 @@ defmodule RDF.IRI do
If the given is not an absolute IRI `nil` is returned.
"""
@spec absolute(t_param, t_param) :: t | nil
def absolute(iri, base) do
cond do
absolute?(iri) -> new(iri)
@ -166,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(t_param, t_param) :: t
def merge(base, rel) do
base
|> parse()
@ -186,6 +196,7 @@ defmodule RDF.IRI do
iex> RDF.IRI.scheme("not an iri")
nil
"""
@spec scheme(t_param) :: String.t | nil
def scheme(iri)
def scheme(%RDF.IRI{value: value}), do: scheme(value)
def scheme(%URI{scheme: scheme}), do: scheme
@ -200,6 +211,7 @@ defmodule RDF.IRI do
@doc """
Parses an IRI into its components and returns them as an `URI` struct.
"""
@spec parse(t_param) :: 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],
@ -215,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}),
@ -247,6 +260,7 @@ defmodule RDF.IRI do
"http://example.com/#Foo"
"""
@spec to_string(t | Datatype.t) :: String.t
def to_string(iri)
def to_string(%RDF.IRI{value: value}),

View file

@ -7,11 +7,11 @@ defmodule RDF.List do
- <https://www.w3.org/TR/rdf11-mt/#rdf-collections>
"""
alias RDF.{Graph, Description, IRI, BlankNode}
alias RDF.{BlankNode, Description, Graph, IRI}
@type t :: %__MODULE__{
head: RDF.IRI.t,
graph: RDF.Graph.t
head: IRI.t,
graph: Graph.t
}
@enforce_keys [:head]
@ -31,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.t_param, Graph.t) :: t
def new(head, graph)
def new(head, graph) when is_atom(head) and head not in ~w[true false nil]a,
@ -70,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),
@ -118,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)
@ -135,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
@ -143,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
@ -168,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

@ -4,23 +4,26 @@ defmodule RDF.Literal do
"""
alias RDF.Datatype.NS.XSD
alias RDF.IRI
@type literal_value ::
binary
| boolean
boolean
| integer
| float
| String.t
| Decimal.t
| Date.t
| Time.t
| DateTime.t
| NaiveDateTime.t
| {Date.t, String.t}
| :nan
@type t :: %__MODULE__{
value: literal_value,
datatype: RDF.IRI.t,
uncanonical_lexical: binary | nil,
language: binary | nil
datatype: IRI.t,
uncanonical_lexical: String.t | nil,
language: String.t | nil
}
defstruct [:value, :datatype, :uncanonical_lexical, :language]
@ -56,6 +59,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
@ -79,6 +83,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),
@ -134,6 +139,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
@ -150,6 +156,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)
@ -162,6 +169,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
@ -174,13 +182,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
@ -196,8 +206,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 """
@ -205,8 +216,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 """
@ -216,6 +228,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
@ -229,10 +242,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)
@ -245,6 +260,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
@ -282,6 +298,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
@ -296,6 +313,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
@ -316,6 +334,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
@ -347,6 +366,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
@ -361,12 +381,13 @@ defmodule RDF.Literal do
|> String.downcase()
|> String.contains?(String.downcase(pattern))
_ ->
raise "Invalid XQuery regex pattern or flags"
# _ ->
# raise "Invalid XQuery regex pattern or flags"
end
end
@doc false
@spec xpath_pattern(t | String.t, t | String.t) :: {:regex, Regex.t} | {:q | :qi, String.t}
def xpath_pattern(pattern, flags)
def xpath_pattern(%RDF.Literal{datatype: @xsd_string} = pattern, flags),
@ -395,6 +416,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.{Datatype, 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 | Datatype.t) :: 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

@ -7,8 +7,15 @@ defmodule RDF.PrefixMap do
alias RDF.IRI
@type prefix :: atom | String.t
@type namespace :: atom | String.t | IRI.t
@type prefix_map :: %{prefix => namespace}
@type conflict_resolver :: (prefix, namespace, namespace -> namespace)
@type t :: %__MODULE__{
map: map
map: prefix_map
}
defstruct map: %{}
@ -17,7 +24,8 @@ defmodule RDF.PrefixMap do
@doc """
Creates an empty `RDF.PrefixMap`.
"""
def new(), do: %__MODULE__{}
@spec new :: t
def new, do: %__MODULE__{}
@doc """
Creates a new `RDF.PrefixMap`.
@ -26,6 +34,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
@ -54,6 +63,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, prefix, 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
@ -73,6 +83,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, prefix, namespace) :: t
def add!(prefix_map, prefix, namespace) do
with {:ok, new_prefix_map} <- add(prefix_map, prefix, namespace) do
new_prefix_map
@ -94,6 +105,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
@ -133,6 +145,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)
@ -185,20 +198,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, prefix) :: t
def delete(prefix_map, prefix)
def delete(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -214,6 +229,7 @@ defmodule RDF.PrefixMap do
If `prefixes` contains prefixes that are not in `prefix_map`, they're simply ignored.
"""
@spec drop(t, [prefix]) :: t
def drop(prefix_map, prefixes)
def drop(%__MODULE__{map: map}, prefixes) do
@ -234,6 +250,7 @@ defmodule RDF.PrefixMap do
Returns `nil`, when the given `prefix` is not present in `prefix_map`.
"""
@spec namespace(t, prefix) :: namespace | nil
def namespace(prefix_map, prefix)
def namespace(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -249,6 +266,7 @@ defmodule RDF.PrefixMap do
Returns `nil`, when the given `namespace` is not present in `prefix_map`.
"""
@spec prefix(t, namespace) :: prefix | nil
def prefix(prefix_map, namespace)
def prefix(%__MODULE__{map: map}, %IRI{} = namespace) do
@ -262,6 +280,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns whether the given prefix exists in the given `RDF.PrefixMap`.
"""
@spec has_prefix?(t, prefix) :: boolean
def has_prefix?(prefix_map, prefix)
def has_prefix?(%__MODULE__{map: map}, prefix) when is_atom(prefix) do
@ -275,6 +294,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns all prefixes from the given `RDF.PrefixMap`.
"""
@spec prefixes(t) :: [prefix]
def prefixes(%__MODULE__{map: map}) do
Map.keys(map)
end
@ -282,6 +302,7 @@ defmodule RDF.PrefixMap do
@doc """
Returns all namespaces from the given `RDF.PrefixMap`.
"""
@spec namespaces(t) :: [namespace]
def namespaces(%__MODULE__{map: map}) do
Map.values(map)
end

View file

@ -6,10 +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.
@ -25,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),
@ -48,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)
@ -81,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
@ -105,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

@ -109,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,10 +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.
@ -25,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),
@ -47,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)
@ -75,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
@ -98,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

@ -9,6 +9,7 @@ defmodule RDF.Vocabulary.Namespace do
the `RDF.NS` module.
"""
alias RDF.Datatype
alias RDF.Utils.ResourceClassifier
@vocabs_dir "priv/vocabs"
@ -295,6 +296,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 +569,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?(Datatype.t) :: boolean
def vocabulary_namespace?(name) do
Code.ensure_compiled?(name) && function_exported?(name, :__base_iri__, 0)
end