-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Aleksei Matiushkin
committed
Feb 4, 2024
1 parent
de8ce3d
commit 3271829
Showing
1 changed file
with
75 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
defmodule Estructura.StreamData do | ||
@moduledoc """ | ||
Additional generators to be used with `__generator__/1` generated by `Use Estructura` | ||
""" | ||
|
||
@typep seed() :: :rand.state() | ||
@typep size() :: non_neg_integer() | ||
@typep generator_fun(a) :: (seed(), size() -> StreamData.LazyTree.t(a)) | ||
|
||
@typedoc """ | ||
An opaque type that represents an `Estructura.StreamData` generator that generates values | ||
of type `a`. | ||
""" | ||
@opaque t(a) :: %StreamData{generator: generator_fun(a)} | Enumerable.t() | ||
|
||
@doc "Helper to generate _unshrinkable_ streams as per `Stream.iterate/2`." | ||
@spec iterate(value, (value -> value)) :: StreamData.t(value) when value: term() | ||
def iterate(initial_value, next_fun) when is_function(next_fun, 1) do | ||
initial_value | ||
|> StreamData.constant() | ||
|> StreamData.bind(&StreamData.constant(next_fun.(&1))) | ||
|> StreamData.unshrinkable() | ||
end | ||
|
||
@spec date(keyword()) :: StreamData.t(Date.t()) | ||
@doc """ | ||
Generates an instance of `Date.t()`. This generator is unshrinkable. | ||
""" | ||
def date(options \\ []) do | ||
from = Keyword.get(options, :from, ~D|2000-01-01|) | ||
to = Keyword.get(options, :to, Date.utc_today()) | ||
step = Keyword.get(options, :step, :random) | ||
|
||
from | ||
|> iterate(fn d -> | ||
next = | ||
case step do | ||
days when is_integer(days) -> Date.add(d, days) | ||
:random -> Date.add(d, Enum.random(-30..30//1)) | ||
{:random, num} -> Date.add(d, Enum.random(-num..num//1)) | ||
end | ||
|
||
case Date.diff(to, next) do | ||
diff when diff <= 0 -> to | ||
diff when diff > 0 -> Date.add(from, diff) | ||
end | ||
end) | ||
end | ||
|
||
@spec datetime(keyword()) :: StreamData.t(DateTime.t()) | ||
@doc """ | ||
Generates an instance of `DateTime.t()`. This generator is unshrinkable. | ||
""" | ||
def datetime(options \\ []) do | ||
from = Keyword.get(options, :from, ~U|2000-01-01T00:00:00Z|) | ||
to = Keyword.get(options, :to, DateTime.utc_now()) | ||
step = Keyword.get(options, :step, :random) | ||
|
||
from | ||
|> iterate(fn dt -> | ||
next = | ||
case step do | ||
secs when is_integer(secs) -> DateTime.add(dt, secs, :second) | ||
{num, unit} when is_integer(num) -> DateTime.add(dt, num, unit) | ||
:random -> DateTime.add(dt, Enum.random(-86_400..86_400//1), :second) | ||
{:random, num} -> DateTime.add(dt, Enum.random(-num..num//1), :second) | ||
end | ||
|
||
case DateTime.diff(to, next) do | ||
diff when diff <= 0 -> to | ||
diff when diff > 0 -> DateTime.add(from, diff) | ||
end | ||
end) | ||
end | ||
end |