rdf-ex/lib/rdf/serialization/serialization.ex
2020-03-10 01:37:53 +01:00

291 lines
8.7 KiB
Elixir

defmodule RDF.Serialization do
@moduledoc """
General functions for working with RDF serializations.
"""
alias RDF.{Dataset, Graph}
@type format :: module
@formats [
RDF.Turtle,
JSON.LD,
RDF.NTriples,
RDF.NQuads,
]
@doc """
The list of all known `RDF.Serialization.Format`s in the RDF.ex eco-system.
Note: Not all known formats might be available to an application, see `available_formats/0`.
## Examples
iex> RDF.Serialization.formats
[RDF.Turtle, JSON.LD, RDF.NTriples, RDF.NQuads]
"""
@spec formats :: [format]
def formats, do: @formats
@doc """
The list of all available `RDF.Serialization.Format`s in an application.
A known format might not be available in an application, when the format is
implemented in an external library and this not specified as a Mix dependency
of this application.
## Examples
iex> RDF.Serialization.available_formats
[RDF.Turtle, RDF.NTriples, RDF.NQuads]
"""
@spec available_formats :: [format]
def available_formats do
Enum.filter @formats, &Code.ensure_loaded?/1
end
@doc """
Returns the `RDF.Serialization.Format` with the given name, if available.
## Examples
iex> RDF.Serialization.format(:turtle)
RDF.Turtle
iex> RDF.Serialization.format("turtle")
RDF.Turtle
iex> RDF.Serialization.format(:jsonld)
nil # unless json_ld is defined as a dependency of the application
"""
@spec format(String.t | atom) :: format | nil
def format(name)
def format(name) when is_binary(name) do
name
|> String.to_existing_atom
|> format()
rescue
ArgumentError -> nil
end
def format(name) do
format_where(fn format -> format.name == name end)
end
@doc """
Returns the `RDF.Serialization.Format` with the given media type, if available.
## Examples
iex> RDF.Serialization.format_by_media_type("text/turtle")
RDF.Turtle
iex> RDF.Serialization.format_by_media_type("application/ld+json")
nil # unless json_ld is defined as a dependency of the application
"""
@spec format_by_media_type(String.t) :: format | nil
def format_by_media_type(media_type) do
format_where(fn format -> format.media_type == media_type end)
end
@doc """
Returns the proper `RDF.Serialization.Format` for the given file extension, if available.
## Examples
iex> RDF.Serialization.format_by_extension("ttl")
RDF.Turtle
iex> RDF.Serialization.format_by_extension(".ttl")
RDF.Turtle
iex> RDF.Serialization.format_by_extension("jsonld")
nil # unless json_ld is defined as a dependency of the application
"""
@spec format_by_extension(String.t) :: format | nil
def format_by_extension(extension)
def format_by_extension("." <> extension), do: format_by_extension(extension)
def format_by_extension(extension) do
format_where(fn format -> format.extension == extension end)
end
defp format_where(fun) do
@formats
|> Stream.filter(&Code.ensure_loaded?/1)
|> Enum.find(fun)
end
@doc """
Reads and decodes a serialized graph or dataset from a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_string(String.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
def read_string(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string(content, opts)
end
end
@doc """
Reads and decodes a serialized graph or dataset from a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
As opposed to `read_string`, it raises an exception if an error occurs.
"""
@spec read_string!(String.t, keyword) :: Graph.t | Dataset.t
def read_string!(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string!(content, opts)
else
{:error, error} -> raise error
end
end
@doc """
Reads and decodes a serialized graph or dataset from a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_file(Path.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
def read_file(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file(file, opts)
end
end
@doc """
Reads and decodes a serialized graph or dataset from a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
As opposed to `read_file`, it raises an exception if an error occurs.
"""
@spec read_file!(Path.t, keyword) :: Graph.t | Dataset.t
def read_file!(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file!(file, opts)
else
{:error, error} -> raise error
end
end
@doc """
Encodes and writes a graph or dataset to a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
It returns an `{:ok, string}` tuple, with `string` being the serialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec write_string(Graph.t | Dataset.t, keyword) :: {:ok, String.t} | {:error, any}
def write_string(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string(data, opts)
end
end
@doc """
Encodes and writes a graph or dataset to a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
As opposed to `write_string`, it raises an exception if an error occurs.
"""
@spec write_string!(Graph.t | Dataset.t, keyword) :: String.t
def write_string!(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string!(data, opts)
else
{:error, error} -> raise error
end
end
@doc """
Encodes and writes a graph or dataset to a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
Other available serialization-independent options:
- `:force` - If not set to `true`, an error is raised when the given file
already exists (default: `false`)
- `:file_mode` - A list with the Elixir `File.open` modes to be used for writing
(default: `[:utf8, :write]`)
It returns `:ok` if successful or `{:error, reason}` if an error occurs.
"""
@spec write_file(Graph.t | Dataset.t, Path.t, keyword) :: :ok | {:error, any}
def write_file(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file(data, path, opts)
end
end
@doc """
Encodes and writes a graph or dataset to a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
See `write_file` for a list of other available options.
As opposed to `write_file`, it raises an exception if an error occurs.
"""
@spec write_file!(Graph.t | Dataset.t, Path.t, keyword) :: :ok
def write_file!(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file!(data, path, opts)
else
{:error, error} -> raise error
end
end
defp string_format(opts) do
if format =
(opts |> Keyword.get(:format) |> format()) ||
(opts |> Keyword.get(:media_type) |> format_by_media_type())
do
{:ok, format}
else
{:error, "unable to detect serialization format"}
end
end
defp file_format(filename, opts) do
case string_format(opts) do
{:ok, format} -> {:ok, format}
_ -> format_by_file_name(filename)
end
end
defp format_by_file_name(filename) do
if format = filename |> Path.extname() |> format_by_extension() do
{:ok, format}
else
{:error, "unable to detect serialization format"}
end
end
end