defmodule Bento do
@moduledoc """
An incredibly fast, correct, pure-Elixir Bencoding library.
This module contains high-level methods to encode and decode Bencoded data.
"""
alias Bento.{Encoder, Decoder, Metainfo}
@doc """
Bencode a value.
iex> Bento.encode([1, "two", [3]])
{:ok, "li1e3:twoli3eee"}
"""
@spec encode(Encoder.bencodable(), Keyword.t()) :: success | failure
when success: {:ok, Encoder.t() | String.t()},
failure: {:error, Encoder.encode_err()}
def encode(value, options \\ []) do
{:ok, encode!(value, options)}
rescue
exception in [Bento.EncodeError] ->
{:error, {:invalid, exception.value}}
end
@doc """
Bencode a value, raising an exception on error.
iex> Bento.encode!([1, "two", [3]])
"li1e3:twoli3eee"
"""
@spec encode!(Encoder.bencodable(), Keyword.t()) :: success | no_return()
when success: Encoder.t() | String.t()
def encode!(value, options \\ [])
def encode!(value, iodata: true), do: Encoder.encode(value)
def encode!(value, _) do
encode!(value, iodata: true) |> IO.iodata_to_binary()
end
@doc """
Bencode a value as iodata.
iex> Bento.encode_to_iodata([1, "two", [3]])
{:ok, [108, [[105, "1", 101], ["3", 58, "two"], [108, [[105, "3", 101]], 101]], 101]}
"""
@spec encode_to_iodata(Encoder.bencodable(), Keyword.t()) :: success | failure
when success: {:ok, iodata()},
failure: {:error, Encoder.encode_err()}
def encode_to_iodata(value, options \\ []) do
encode(value, Keyword.merge(options, iodata: true))
end
@doc """
Bencode a value as iodata, raises an exception on error.
iex> Bento.encode_to_iodata!([1, "two", [3]])
[108, [[105, "1", 101], ["3", 58, "two"], [108, [[105, "3", 101]], 101]], 101]
"""
@spec encode_to_iodata!(Encoder.bencodable(), Keyword.t()) :: iodata() | no_return()
def encode_to_iodata!(value, options \\ []) do
encode!(value, Keyword.merge(options, iodata: true))
end
@doc """
Decode bencoded data to a value.
iex> Bento.decode("li1e3:twoli3eee")
{:ok, [1, "two", [3]]}
Use `:as` as option to transform the parsed value into a struct.
defmodule User do
defstruct name: "John", age: 27
end
Bento.decode("d4:name3:Bobe", as: %User{})
# {:ok, %User{name: "Bob", age: 27}}
"""
@spec decode(iodata(), Decoder.opts()) :: {:ok, Decoder.t()} | failure
when failure: {:error, Decoder.decode_err()}
def decode(iodata, options \\ []), do: Decoder.decode(iodata, options)
@doc """
Decode bencoded data to a value, raising an exception on error.
iex> Bento.decode!("li1e3:twoli3eee")
[1, "two", [3]]
Use `:as` as option to transform the parsed value into a struct.
defmodule User do
defstruct name: "John", age: 27
end
Bento.decode!("d4:name3:Bobe", as: %User{})
# %User{name: "Bob", age: 27}
"""
@spec decode!(iodata(), Decoder.opts()) :: Decoder.t() | no_return()
def decode!(iodata, options \\ []), do: Decoder.decode!(iodata, options)
@doc """
Like `decode`, but ensures the data is a valid torrent metainfo file.
"""
@spec torrent(iodata()) :: {:ok, Metainfo.Torrent.t()} | failure
when failure: {:error, Decoder.decode_err() | String.t()}
def torrent(iodata) do
with {:ok, decoded} <- decode(iodata, as: %Metainfo.Torrent{}),
{:ok, info} <- Metainfo.info(decoded) do
{:ok, struct(decoded, info: info)}
end
end
@doc """
Like `decode!`, but ensures the data is a valid torrent metainfo file.
"""
@spec torrent!(iodata()) :: Metainfo.Torrent.t() | no_return()
def torrent!(iodata) do
decoded = decode!(iodata, as: %Metainfo.Torrent{})
struct(decoded, info: Metainfo.info!(decoded))
end
end