From 6a9daa30e6ba3dc8187513e7d47170a1e5e7c789 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 27 Nov 2016 13:49:42 +0100 Subject: [PATCH] core: Access behaviour for Graph --- lib/rdf/graph.ex | 99 ++++++++++++++++++++++++++++++++++++++++ test/unit/graph_test.exs | 10 +++- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index e717a9d..d420fb0 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -8,6 +8,8 @@ defmodule RDF.Graph do """ defstruct name: nil, descriptions: %{} + @behaviour Access + alias RDF.{Description, Triple} @type t :: module @@ -155,6 +157,103 @@ defmodule RDF.Graph do end end + def put(graph, subject, predications = {_predicate, _objects}), + do: put(graph, subject, [predications]) + + + @doc """ + Fetches the description of the given subject. + + When the subject can not be found `:error` is returned. + + # Examples + + iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |> + ...> RDF.Graph.fetch(EX.S1) + {:ok, RDF.Description.new({EX.S1, EX.P1, EX.O1})} + iex> RDF.Graph.fetch(RDF.Graph.new, EX.foo) + :error + """ + def fetch(%RDF.Graph{descriptions: descriptions}, subject) do + Access.fetch(descriptions, Triple.convert_subject(subject)) + end + + @doc """ + Gets the description of the given subject. + + When the subject can not be found the optionally given default value or `nil` is returned. + + # Examples + + iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |> + ...> RDF.Graph.get(EX.S1) + RDF.Description.new({EX.S1, EX.P1, EX.O1}) + iex> RDF.Graph.get(RDF.Graph.new, EX.Foo) + nil + iex> RDF.Graph.get(RDF.Graph.new, EX.Foo, :bar) + :bar + """ + def get(graph = %RDF.Graph{}, subject, default \\ nil) do + case fetch(graph, subject) do + {:ok, value} -> value + :error -> default + end + end + + @doc """ + Gets and updates the description of the given subject, in a single pass. + + Invokes the passed function on the `RDF.Description` of the given subject; + this function should return either `{description_to_return, new_description}` or `:pop`. + + If the passed function returns `{description_to_return, new_description}`, the + return value of `get_and_update` is `{description_to_return, new_graph}` where + `new_graph` is the input `Graph` updated with `new_description` for + the given subject. + + If the passed function returns `:pop` the description for the given subject is + removed and a `{removed_description, new_graph}` tuple gets returned. + + # Examples + + iex> RDF.Graph.new({EX.S, EX.P, EX.O}) |> + ...> RDF.Graph.get_and_update(EX.S, fn current_description -> + ...> {current_description, {EX.P, EX.NEW}} + ...> end) + {RDF.Description.new(EX.S, EX.P, EX.O), RDF.Graph.new(EX.S, EX.P, EX.NEW)} + """ + def get_and_update(graph = %RDF.Graph{}, subject, fun) do + with triple_subject = Triple.convert_subject(subject) do + case fun.(get(graph, triple_subject)) do + {old_description, new_description} -> + {old_description, put(graph, triple_subject, new_description)} + :pop -> pop(graph, triple_subject) + end + end + end + + @doc """ + Pops the description of the given subject. + + When the subject can not be found the optionally given default value or `nil` is returned. + + # Examples + + iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |> + ...> RDF.Graph.pop(EX.S1) + {RDF.Description.new({EX.S1, EX.P1, EX.O1}), RDF.Graph.new({EX.S2, EX.P2, EX.O2})} + iex> RDF.Graph.pop(RDF.Graph.new({EX.S, EX.P, EX.O}), EX.Missing) + {nil, RDF.Graph.new({EX.S, EX.P, EX.O})} + """ + def pop(graph = %RDF.Graph{name: name, descriptions: descriptions}, subject) do + case Access.pop(descriptions, Triple.convert_subject(subject)) do + {nil, _} -> + {nil, graph} + {description, new_descriptions} -> + {description, %RDF.Graph{name: name, descriptions: new_descriptions}} + end + end + def subject_count(graph), do: Enum.count(graph.descriptions) diff --git a/test/unit/graph_test.exs b/test/unit/graph_test.exs index 69a9848..85faa91 100644 --- a/test/unit/graph_test.exs +++ b/test/unit/graph_test.exs @@ -7,7 +7,7 @@ defmodule RDF.GraphTest do doctest RDF.Graph alias RDF.{Graph, Description} - import RDF, only: [uri: 1, literal: 1, bnode: 1] + import RDF, only: [uri: 1, bnode: 1] def graph, do: unnamed_graph @@ -220,4 +220,12 @@ defmodule RDF.GraphTest do end end + describe "Access behaviour" do + test "access with the [] operator" do + assert Graph.new[EX.Subject] == nil + assert Graph.new({EX.S, EX.p, EX.O})[EX.S] == + Description.new({EX.S, EX.p, EX.O}) + end + end + end