Change Inspect form for RDF data structures to be Turtle-based

This commit is contained in:
Marcel Otto 2020-11-02 15:17:18 +01:00
parent 929e2a8c81
commit 8d98461e0b
3 changed files with 168 additions and 72 deletions

View file

@ -15,10 +15,12 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
### Changed
- the Inspect form of the RDF data structures are now Turtle-based and respect
the usual `:limit` behaviour
- more compact Inspect form for `RDF.PrefixMap`
- the `RDF.Turtle.Encoder` accepts `RDF.Vocabulary.Namespace` modules as `base`
- the performance of the `RDF.Turtle.Encoder` was improved (by using a more
efficient method for resolving IRIs to prefixed names for most use cases)
- the performance of the `RDF.Turtle.Encoder` was improved (by using a for most
use cases more efficient method for resolving IRIs to prefixed names)
- `RDF.BlankNode.new/0` creates integer-based blank nodes, which is much more
efficient in terms of performance and memory consumption than the previous
ref-based blank nodes

View file

@ -1,43 +1,3 @@
defmodule RDF.InspectHelper do
@moduledoc false
import Inspect.Algebra
def objects_doc(objects, opts) do
objects
|> Enum.map(fn {object, _} -> to_doc(object, opts) end)
|> fold_doc(fn object, acc -> line(object, acc) end)
end
def predications_doc(predications, opts) do
predications
|> Enum.map(fn {predicate, objects} ->
to_doc(predicate, opts)
|> line(objects_doc(objects, opts))
|> nest(4)
end)
|> fold_doc(fn predication, acc ->
line(predication, acc)
end)
end
def descriptions_doc(descriptions, opts) do
descriptions
|> Enum.map(fn {subject, description} ->
to_doc(subject, opts)
|> line(predications_doc(description.predications, opts))
|> nest(4)
end)
|> fold_doc(fn predication, acc ->
line(predication, acc)
end)
end
def surround_doc(left, doc, right) do
concat(concat(left, nest(doc, 1)), right)
end
end
defimpl Inspect, for: RDF.IRI do
def inspect(%RDF.IRI{value: value}, _opts) do
"~I<#{value}>"
@ -57,49 +17,100 @@ defimpl Inspect, for: RDF.Literal do
end
defimpl Inspect, for: RDF.Description do
import Inspect.Algebra
import RDF.InspectHelper
def inspect(description, opts) do
if opts.structs do
try do
limit = opts.limit < RDF.Description.statement_count(description)
def inspect(%RDF.Description{subject: subject, predications: predications}, opts) do
doc =
space("subject:", to_doc(subject, opts))
|> line(predications_doc(predications, opts))
|> nest(4)
description =
if limit do
description.subject
|> RDF.Description.new(init: Enum.take(description, opts.limit))
else
description
end
surround_doc("#RDF.Description{", doc, "}")
body =
description
|> RDF.Turtle.write_string!(only: :triples)
|> String.trim_trailing()
"#RDF.Description\n#{body}#{if limit, do: "..\n..."}"
rescue
caught_exception ->
message =
"got #{inspect(caught_exception.__struct__)} with message " <>
"#{inspect(Exception.message(caught_exception))} while inspecting RDF.Description #{
description.subject
}"
exception = Inspect.Error.exception(message: message)
if opts.safe do
Inspect.inspect(exception, opts)
else
reraise(exception, __STACKTRACE__)
end
end
else
Inspect.Map.inspect(description, opts)
end
end
end
defimpl Inspect, for: RDF.Graph do
import Inspect.Algebra
import RDF.InspectHelper
def inspect(graph, opts) do
if opts.structs do
try do
limit = opts.limit < RDF.Graph.statement_count(graph)
def inspect(%RDF.Graph{name: name, descriptions: descriptions}, opts) do
doc =
space("name:", to_doc(name, opts))
|> line(descriptions_doc(descriptions, opts))
|> nest(4)
graph =
if limit do
graph
|> RDF.Graph.clear()
|> RDF.Graph.add(Enum.take(graph, opts.limit))
else
graph
end
surround_doc("#RDF.Graph{", doc, "}")
header = "#RDF.Graph name: #{inspect(graph.name)}"
body =
graph
|> RDF.Turtle.write_string!()
|> String.trim_trailing()
"#{header}\n#{body}#{if limit, do: "..\n..."}"
rescue
caught_exception ->
message =
"got #{inspect(caught_exception.__struct__)} with message " <>
"#{inspect(Exception.message(caught_exception))} while inspecting RDF.Graph #{
graph.name
}"
exception = Inspect.Error.exception(message: message)
if opts.safe do
Inspect.inspect(exception, opts)
else
reraise(exception, __STACKTRACE__)
end
end
else
Inspect.Map.inspect(graph, opts)
end
end
end
defimpl Inspect, for: RDF.Dataset do
import Inspect.Algebra
import RDF.InspectHelper
def inspect(%RDF.Dataset{name: name} = dataset, opts) do
doc =
space("name:", to_doc(name, opts))
|> line(graphs_doc(RDF.Dataset.graphs(dataset), opts))
|> nest(4)
surround_doc("#RDF.Dataset{", doc, "}")
end
defp graphs_doc(graphs, opts) do
graphs
|> Enum.map(fn graph -> to_doc(graph, opts) end)
|> fold_doc(fn graph, acc -> line(graph, acc) end)
def inspect(dataset, opts) do
map = [name: dataset.name, graph_names: Map.keys(dataset.graphs)]
open = color("%RDF.Dataset{", :map, opts)
sep = color(",", :map, opts)
close = color("}", :map, opts)
container_doc(open, map, close, opts, &Inspect.List.keyword/2, separator: sep, break: :strict)
end
end

View file

@ -0,0 +1,83 @@
defmodule RDF.InspectTest do
use RDF.Test.Case
alias RDF.Turtle
alias RDF.NS.RDFS
@test_description EX.S
|> RDF.type(RDFS.Class)
|> EX.p("foo", 42)
@test_graph Graph.new(
[
EX.S1
|> EX.p1(EX.O1)
|> EX.p2("foo", 42),
EX.S2
|> EX.p3(EX.O3)
],
prefixes: [ex: EX]
)
describe "RDF.Description" do
test "it includes a header" do
{header, _} = inspect_parts(@test_description)
assert header == "#RDF.Description"
end
test "it encodes the description in Turtle" do
{_, body} = inspect_parts(@test_description)
assert body ==
Turtle.write_string!(@test_description, only: :triples) |> String.trim()
end
test ":limit option" do
{_, triples} = inspect_parts(@test_description, limit: 2)
assert triples ==
(EX.S
|> EX.p("foo", 42)
|> Turtle.write_string!(only: :triples)
|> String.trim()) <>
"..\n..."
end
end
describe "RDF.Graph" do
test "it includes a header with the graph name" do
{header, _} = inspect_parts(@test_graph)
assert header == "#RDF.Graph name: nil"
graph_name = RDF.iri(EX.Graph)
{header, _} = @test_graph |> Graph.change_name(graph_name) |> inspect_parts()
assert header == "#RDF.Graph name: #{inspect(graph_name)}"
end
test "it encodes the graph in Turtle" do
{_, body} = inspect_parts(@test_graph)
assert body == Turtle.write_string!(@test_graph) |> String.trim()
end
test ":limit option" do
{_, body} = inspect_parts(@test_graph, limit: 2)
assert body ==
(Graph.new(
EX.S1
|> EX.p1(EX.O1)
|> EX.p2(42),
prefixes: [ex: EX]
)
|> Turtle.write_string!()
|> String.trim()) <>
"..\n..."
end
end
def inspect_parts(graph, opts \\ []) do
inspect_form = inspect(graph, opts)
[header, body] = String.split(inspect_form, "\n", parts: 2)
{header, body}
end
end