diff --git a/lib/membrane/element/base/mixin/common_behaviour.ex b/lib/membrane/element/base/mixin/common_behaviour.ex index 6804edb0c..d49d756bb 100644 --- a/lib/membrane/element/base/mixin/common_behaviour.ex +++ b/lib/membrane/element/base/mixin/common_behaviour.ex @@ -7,10 +7,12 @@ defmodule Membrane.Element.Base.Mixin.CommonBehaviour do For more information on implementing elements, see `Membrane.Element.Base`. """ - alias Membrane.{Action, Core, Element, Event} + alias Membrane.{Action, Core, Element, Event, Time} alias Core.CallbackHandler alias Element.{Action, CallbackContext, Pad} + use Bunch + @typedoc """ Type that defines all valid return values from most callbacks. """ @@ -148,31 +150,14 @@ defmodule Membrane.Element.Base.Mixin.CommonBehaviour do """ @callback handle_shutdown(state :: Element.state_t()) :: :ok - @default_quoted_specs %{ - atom: - quote do - atom() - end, - boolean: - quote do - boolean() - end, - string: - quote do - String.t() - end, - keyword: - quote do - keyword() - end, - struct: - quote do - struct() - end, - caps: - quote do - struct() - end + @default_types_params %{ + atom: [spec: quote_expr(atom)], + boolean: [spec: quote_expr(boolean)], + string: [spec: quote_expr(String.t())], + keyword: [spec: quote_expr(keyword)], + struct: [spec: quote_expr(struct)], + caps: [spec: quote_expr(struct)], + time: [spec: quote_expr(Time.t()), inspector: &Time.to_code_str/1] } @doc """ @@ -185,10 +170,12 @@ defmodule Membrane.Element.Base.Mixin.CommonBehaviour do * `type:` atom, used for parsing * `spec:` typespec for value in struct. If ommitted, for types: - `#{inspect(Map.keys(@default_quoted_specs))}` the default typespec is provided. + `#{inspect(Map.keys(@default_types_params))}` the default typespec is provided. For others typespec is set to `t:any/0` * `default:` default value for option. If not present, value for this option will have to be provided each time options struct is created + * `inspector:` function converting fields' value to a string. Used when + creating documentation instead of `inspect/1` * `description:` string describing an option. It will be present in value returned by `options/0` and in typedoc for the struct. """ @@ -203,8 +190,15 @@ defmodule Membrane.Element.Base.Mixin.CommonBehaviour do default_val_desc = if Keyword.has_key?(v, :default) do + inspector = + v + |> Keyword.get( + :inspector, + @default_types_params[v[:type]][:inspector] || quote(do: &inspect/1) + ) + quote do - "Defaults to `#{inspect(unquote(v)[:default])}`" + "Defaults to `#{unquote(inspector).(unquote(v)[:default])}`" end else "" @@ -263,12 +257,7 @@ defmodule Membrane.Element.Base.Mixin.CommonBehaviour do with_default_specs = kw |> Enum.map(fn {k, v} -> - quoted_any = - quote do - any() - end - - default_val = @default_quoted_specs |> Map.get(v[:type], quoted_any) + default_val = @default_types_params[v[:type]][:spec] || quote_expr(any) {k, v |> Keyword.put_new(:spec, default_val)} end) diff --git a/lib/membrane/time.ex b/lib/membrane/time.ex index 2dd5c4802..ee1ef4cc6 100644 --- a/lib/membrane/time.ex +++ b/lib/membrane/time.ex @@ -50,6 +50,16 @@ defmodule Membrane.Time do @type non_neg_t :: non_neg_integer @type native_t :: integer + @units_abbreviations %{ + days: "d", + hours: "h", + minutes: "min", + seconds: "s", + milliseconds: "ms", + microseconds: "us", + nanoseconds: "ns" + } + @doc """ Checks whether value is Membrane.Time.t """ @@ -60,6 +70,60 @@ defmodule Membrane.Time do """ defguard is_native_t(value) when is_integer(value) + @doc """ + Returns duration as a string with unit. Chosen unit is the biggest possible + that doesn't involve precission loss. + + ## Examples + + iex> import #{inspect(__MODULE__)} + iex> 10 |> milliseconds() |> pretty_duration() + "10 ms" + iex> 60_000_000 |> microseconds() |> pretty_duration() + "1 min" + iex> 2 |> nanoseconds() |> pretty_duration() + "2 ns" + + """ + @spec pretty_duration(t) :: String.t() + def pretty_duration(time) when is_t(time) do + {time, unit} = time |> best_unit() + + "#{time} #{@units_abbreviations[unit]}" + end + + @doc """ + Returns quoted code producing given amount time. Chosen unit is the biggest possible + that doesn't involve precission loss. + + ## Examples + + iex> import #{inspect(__MODULE__)} + iex> 10 |> milliseconds() |> to_code() |> Macro.to_string() + quote do 10 |> Membrane.Time.milliseconds() end |> Macro.to_string() + iex> 60_000_000 |> microseconds() |> to_code() |> Macro.to_string() + quote do 1 |> Membrane.Time.minutes() end |> Macro.to_string() + iex> 2 |> nanoseconds() |> to_code() |> Macro.to_string() + quote do 2 |> #{inspect(__MODULE__)}.nanoseconds() end |> Macro.to_string() + + """ + @spec to_code(t) :: Macro.t() + def to_code(time) when is_t(time) do + {time, unit} = time |> best_unit() + + quote do + unquote(time) |> unquote(__MODULE__).unquote(unit)() + end + end + + @doc """ + Returns string representation of result of `to_code/1`. + """ + @spec pretty_duration(t) :: Macro.t() + def to_code_str(time) when is_t(time) do + time |> to_code() |> Macro.to_string() + end + @doc """ Returns current time in pretty format (currently iso8601), as string Uses system_time/0 under the hood. @@ -372,4 +436,23 @@ defmodule Membrane.Time do def to_days(value) when is_t(value) do (value / (1 |> day)) |> round end + + @spec units() :: Keyword.t(t) + defp units() do + [ + days: 1 |> day(), + hours: 1 |> hour(), + minutes: 1 |> minute(), + seconds: 1 |> second(), + milliseconds: 1 |> millisecond(), + microseconds: 1 |> microsecond(), + nanoseconds: 1 |> nanosecond() + ] + end + + defp best_unit(time) do + {unit, divisor} = units() |> Enum.find(fn {_unit, divisor} -> time |> rem(divisor) == 0 end) + + {time |> div(divisor), unit} + end end diff --git a/mix.exs b/mix.exs index 578983e0e..1f0d815fc 100644 --- a/mix.exs +++ b/mix.exs @@ -62,7 +62,8 @@ defmodule Membrane.Mixfile do {:espec, "~> 1.6", only: :test}, {:excoveralls, "~> 0.8", only: :test}, {:qex, "~> 0.3"}, - {:bunch, "~> 0.1.2"} + # {:bunch, "~> 0.1.2"} + {:bunch, github: "membraneframework/bunch"} ] end end diff --git a/mix.lock b/mix.lock index 597afe102..5429b4e4b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "bunch": {:hex, :bunch, "0.1.2", "2389a4bcdb62382fbfeed9e19952cc22452dc0c01b4bcac941cce00ef212d7b4", [:mix], [], "hexpm"}, + "bunch": {:git, "https://github.com/membraneframework/bunch.git", "ad163a027e7dce7fedd09313cdb78edca2188604", []}, "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, "espec": {:hex, :espec, "1.6.3", "d9355788e508b82743a1b1b9aa5ac64ba37b0547c6210328d909e8a6eb56d42e", [:mix], [{:meck, "0.8.12", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/test/membrane/time_test.exs b/test/membrane/time_test.exs new file mode 100644 index 000000000..a1f381658 --- /dev/null +++ b/test/membrane/time_test.exs @@ -0,0 +1,7 @@ +defmodule Membrane.TimeTest do + use ExUnit.Case, async: true + + @module Membrane.Time + + doctest @module +end