From b445f2e31c18f2e3300a71a3b160480a98e51c02 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Mon, 30 Oct 2017 11:40:44 +0100 Subject: [PATCH] Add Collectable implementations for all RDF data structures --- CHANGELOG.md | 13 ++++++++++++ README.md | 2 +- VERSION | 2 +- lib/rdf/dataset.ex | 17 ++++++++++++++++ lib/rdf/description.ex | 15 ++++++++++++++ lib/rdf/graph.ex | 15 ++++++++++++++ test/unit/dataset_test.exs | 21 ++++++++++++++++++++ test/unit/description_test.exs | 36 +++++++++++++++++++++++++++++++++- test/unit/graph_test.exs | 19 ++++++++++++++++++ 9 files changed, 137 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59bc55a..4516cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com). +## Unreleased + +### Added + +- `Collectable` implementations for all `RDF.Data` structures so they can be + used as destinations of `Enum.into` and `for` comprehensions + + +[Compare v0.3.0...HEAD](https://github.com/marcelotto/rdf-ex/compare/v0.3.0...HEAD) + + + + ## 0.3.0 - 2017-08-24 ### Added diff --git a/README.md b/README.md index 4f485c2..f836533 100644 --- a/README.md +++ b/README.md @@ -425,7 +425,7 @@ RDF.ex provides various data structures for collections of statements: - `RDF.Graph`: a named collection of statements - `RDF.Dataset`: a named collection of graphs, i.e. a collection of statements from different graphs; it may have multiple named graphs and at most one unnamed ("default") graph -All of these structures have similar sets of functions and implement Elixirs `Enumerable` protocol, Elixirs `Access` behaviour and the `RDF.Data` protocol of RDF.ex. +All of these structures have similar sets of functions and implement Elixirs `Enumerable` and `Collectable` protocol, Elixirs `Access` behaviour and the `RDF.Data` protocol of RDF.ex. The `new` function of these data structures create new instances of the struct and optionally initialize them with initial statements. `RDF.Description.new` requires at least an IRI or blank node for the subject, while `RDF.Graph.new` and `RDF.Dataset.new` take an optional IRI for the name of the graph or dataset. diff --git a/VERSION b/VERSION index 0d91a54..746bd19 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1-dev diff --git a/lib/rdf/dataset.ex b/lib/rdf/dataset.ex index 147f3a4..5c80796 100644 --- a/lib/rdf/dataset.ex +++ b/lib/rdf/dataset.ex @@ -724,6 +724,7 @@ defmodule RDF.Dataset do end end + defimpl Enumerable do def member?(graph, statement), do: {:ok, RDF.Dataset.include?(graph, statement)} def count(graph), do: {:ok, RDF.Dataset.statement_count(graph)} @@ -741,4 +742,20 @@ defmodule RDF.Dataset do {:suspended, acc, &reduce(dataset, &1, fun)} end end + + + defimpl Collectable do + def into(original) do + collector_fun = fn + dataset, {:cont, list} when is_list(list) + -> RDF.Dataset.add(dataset, List.to_tuple(list)) + dataset, {:cont, elem} -> RDF.Dataset.add(dataset, elem) + dataset, :done -> dataset + _dataset, :halt -> :ok + end + + {original, collector_fun} + end + end + end diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index 067cf1b..08787e1 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -587,4 +587,19 @@ defmodule RDF.Description do end end + + defimpl Collectable do + def into(original) do + collector_fun = fn + description, {:cont, list} when is_list(list) + -> RDF.Description.add(description, List.to_tuple(list)) + description, {:cont, elem} -> RDF.Description.add(description, elem) + description, :done -> description + _description, :halt -> :ok + end + + {original, collector_fun} + end + end + end diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index aa91d56..8a22a27 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -617,5 +617,20 @@ defmodule RDF.Graph do end end + + defimpl Collectable do + def into(original) do + collector_fun = fn + graph, {:cont, list} when is_list(list) + -> RDF.Graph.add(graph, List.to_tuple(list)) + graph, {:cont, elem} -> RDF.Graph.add(graph, elem) + graph, :done -> graph + _graph, :halt -> :ok + end + + {original, collector_fun} + end + end + end diff --git a/test/unit/dataset_test.exs b/test/unit/dataset_test.exs index cad6d5d..84b9459 100644 --- a/test/unit/dataset_test.exs +++ b/test/unit/dataset_test.exs @@ -742,6 +742,27 @@ defmodule RDF.DatasetTest do end end + describe "Collectable protocol" do + test "with a list of triples" do + triples = [ + {EX.Subject, EX.predicate1, EX.Object1}, + {EX.Subject, EX.predicate2, EX.Object2}, + {EX.Subject, EX.predicate2, EX.Object2, EX.Graph} + ] + assert Enum.into(triples, Dataset.new()) == Dataset.new(triples) + end + + test "with a list of lists" do + lists = [ + [EX.Subject, EX.predicate1, EX.Object1], + [EX.Subject, EX.predicate2, EX.Object2], + [EX.Subject, EX.predicate2, EX.Object2, EX.Graph] + ] + assert Enum.into(lists, Dataset.new()) == + Dataset.new(Enum.map(lists, &List.to_tuple/1)) + end + end + describe "Access behaviour" do test "access with the [] operator" do assert Dataset.new[EX.Graph] == nil diff --git a/test/unit/description_test.exs b/test/unit/description_test.exs index d50fee8..24f4697 100644 --- a/test/unit/description_test.exs +++ b/test/unit/description_test.exs @@ -370,6 +370,41 @@ defmodule RDF.DescriptionTest do end end + describe "Collectable protocol" do + test "with a map" do + map = %{ + EX.predicate1 => EX.Object1, + EX.predicate2 => EX.Object2 + } + assert Enum.into(map, Description.new(EX.Subject)) == Description.new(EX.Subject, map) + end + + test "with a list of triples" do + triples = [ + {EX.Subject, EX.predicate1, EX.Object1}, + {EX.Subject, EX.predicate2, EX.Object2} + ] + assert Enum.into(triples, Description.new(EX.Subject)) == Description.new(triples) + end + + test "with a list of predicate-object pairs" do + pairs = [ + {EX.predicate1, EX.Object1}, + {EX.predicate2, EX.Object2} + ] + assert Enum.into(pairs, Description.new(EX.Subject)) == Description.new(EX.Subject, pairs) + end + + test "with a list of lists" do + lists = [ + [EX.Subject, EX.predicate1, EX.Object1], + [EX.Subject, EX.predicate2, EX.Object2] + ] + assert Enum.into(lists, Description.new(EX.Subject)) == + Description.new(Enum.map(lists, &List.to_tuple/1)) + end + end + describe "Access behaviour" do test "access with the [] operator" do assert Description.new(EX.Subject)[EX.predicate] == nil @@ -381,7 +416,6 @@ defmodule RDF.DescriptionTest do {EX.Subject, EX.predicate2, EX.Object3}])[EX.predicate1] == [iri(EX.Object1), iri(EX.Object2)] end - end end diff --git a/test/unit/graph_test.exs b/test/unit/graph_test.exs index c368f8f..ab4183d 100644 --- a/test/unit/graph_test.exs +++ b/test/unit/graph_test.exs @@ -403,6 +403,25 @@ defmodule RDF.GraphTest do end end + describe "Collectable protocol" do + test "with a list of triples" do + triples = [ + {EX.Subject, EX.predicate1, EX.Object1}, + {EX.Subject, EX.predicate2, EX.Object2} + ] + assert Enum.into(triples, Graph.new()) == Graph.new(triples) + end + + test "with a list of lists" do + lists = [ + [EX.Subject, EX.predicate1, EX.Object1], + [EX.Subject, EX.predicate2, EX.Object2] + ] + assert Enum.into(lists, Graph.new()) == + Graph.new(Enum.map(lists, &List.to_tuple/1)) + end + end + describe "Access behaviour" do test "access with the [] operator" do assert Graph.new[EX.Subject] == nil