defmodule EverFaker.Util do import EverFaker, only: [localize: 1] @moduledoc """ Collection of useful functions for your fake data. Functions aware of locale. """ @doc """ Pick a random element from an enumerable. Can also be provided a blacklist enumerable as a second argument. ## Examples iex> EverFaker.Util.pick(10..100) 79 iex> EverFaker.Util.pick([1, 3, 5, 7]) 3 iex> EverFaker.Util.pick([true, false, nil]) true iex> EverFaker.Util.pick(["a", "b", "c"], ["b"]) "a" iex> EverFaker.Util.pick([1, "2", 3.0], 1..10) "2" """ @spec pick(Enum.t(), Enum.t()) :: any def pick(enum, blacklist) def pick(enum, blacklist) do Enum.reject(enum, fn el -> Enum.member?(blacklist, el) end) |> pick() end def pick(enum) do Enum.at(enum, EverFaker.random_between(0, Enum.count(enum) - 1)) end @doc """ Generate N unique elements ## Examples iex> EverFaker.Util.sample_uniq(2, &EverFaker.Internet.email/0) ["conor2058@schiller.com", "elizabeth2056@rolfson.net"] iex> EverFaker.Util.sample_uniq(10, fn -> EverFaker.String.base64(4) end) [ "0CzJ", "3nuk", "D1Ip", "IqFG", "My3J", "W3yW", "e/kH", "gd75", "hVCK", "snJn" ] iex> EverFaker.Util.sample_uniq(1, &EverFaker.Phone.EnUs.area_code/0) ["508"] iex> EverFaker.Util.sample_uniq(0, &EverFaker.Internet.email/0) ** (FunctionClauseError) no function clause matching in EverFaker.Util.sample_uniq/3 """ @spec sample_uniq(pos_integer, (-> any), MapSet.t()) :: [any] def sample_uniq(count, sampler, acc \\ MapSet.new()) when is_integer(count) and count > 0 and is_function(sampler, 0) do case MapSet.size(acc) do ^count -> MapSet.to_list(acc) _ -> acc = MapSet.put(acc, sampler.()) sample_uniq(count, sampler, acc) end end @doc """ Execute fun n times with the index as first param and return the results as a list ## Examples iex> EverFaker.Util.list(3, &(&1)) [0, 1, 2] iex> EverFaker.Util.list(3, &(&1 + 1)) [1, 2, 3] iex> EverFaker.Util.list(5, &(&1 * &1)) [0, 1, 4, 9, 16] iex> EverFaker.Util.list(3, &(to_string(&1))) ["0", "1", "2"] """ @spec list(integer, (integer -> any)) :: [any] def list(n, fun) when is_function(fun, 1) do Enum.map(0..(n - 1), &fun.(&1)) end @spec list(integer, (-> any)) :: [any] def list(n, fun) when is_function(fun, 0) do Enum.map(0..(n - 1), fn _ -> fun.() end) end @doc """ Execute fun n times with the index as first param and join the results with joiner ## Examples iex> EverFaker.Util.join(3, ", ", &EverFaker.Code.isbn13/0) "9781542646109, 9783297052358, 9790203032090" iex> EverFaker.Util.join(4, "-", fn -> EverFaker.format("####") end) "7337-6033-7459-8109" iex> EverFaker.Util.join(2, " vs ", &EverFaker.Superhero.name/0) "Falcon vs Green Blink Claw" iex> EverFaker.Util.join(2, " or ", &EverFaker.Color.name/0) "Purple or White" """ @spec join(integer, binary, (-> binary)) :: binary def join(n, joiner \\ "", fun) do Enum.join(list(n, fun), joiner) end @doc """ Get a random digit as a string; one of 0-9 ## Examples iex> EverFaker.Util.digit() "0" iex> EverFaker.Util.digit() "1" iex> EverFaker.Util.digit() "5" iex> EverFaker.Util.digit() "4" """ @spec digit() :: binary localize(:digit) @doc """ Converts a list to a string, with "and" before the last item. Uses an Oxford comma. ## Examples iex> EverFaker.Util.to_sentence(["Black", "White"]) "Black and White" iex> EverFaker.Util.to_sentence(["Jon Snow"]) "Jon Snow" iex> EverFaker.Util.to_sentence(["Oceane", "Angeline", "Nicholas"]) "Angeline, Nicholas, and Oceane" iex> EverFaker.Util.to_sentence(["One", "Two", "Three", "Four"]) "Two, Three, Four, and One" """ @spec to_sentence([binary]) :: binary def to_sentence(items) do Module.concat(__MODULE__, EverFaker.mlocale()).to_sentence(items) end @doc """ Get a random alphabet character as a string; one of a-z or A-Z ## Examples iex> EverFaker.Util.letter() "E" iex> EverFaker.Util.letter() "L" iex> EverFaker.Util.letter() "R" iex> EverFaker.Util.letter() "C" iex> EverFaker.Util.letter() "e" """ @spec letter() :: binary localize(:letter) @doc """ Get a random lowercase character as a string; one of a-z ## Examples iex> EverFaker.Util.lower_letter() "e" iex> EverFaker.Util.lower_letter() "l" iex> EverFaker.Util.lower_letter() "r" iex> EverFaker.Util.lower_letter() "c" """ @spec lower_letter() :: binary localize(:lower_letter) @doc """ Get a random uppercase character as a string; one of A-Z ## Examples iex> EverFaker.Util.upper_letter() "E" iex> EverFaker.Util.upper_letter() "L" iex> EverFaker.Util.upper_letter() "R" iex> EverFaker.Util.upper_letter() "C" """ @spec upper_letter() :: binary localize(:upper_letter) @doc """ Start a cycle. See cycle/1 """ @spec cycle_start([any]) :: pid def cycle_start(items) do {:ok, cycle_pid} = Agent.start_link(fn -> {[], items} end) cycle_pid end @doc """ Cycle randomly through the given list with guarantee every element of the list is used once before elements are being picked again. This is done by keeping a list of remaining elements that have not been picked yet. The list of remaining element is returned, as well as the randomly picked element. """ @spec cycle(pid) :: any def cycle(cycle_pid) do Agent.get_and_update(cycle_pid, fn {[], items} -> [h | t] = EverFaker.shuffle(items) {h, {t, items}} {[h | t], items} -> {h, {t, items}} end) end @doc """ Format a string with randomly generated data. Format specifiers are replaced by random values. A format specifier follows this prototype: %[length]specifier The following specifier rules are present by default: - **d**: digits 0-9 - **a**: lowercase letter a-z - **A**: uppercase letter A-Z - **b**: anycase letter a-z, A-Z The specifier rules can be overridden using the second argument. ## Examples iex> EverFaker.Util.format("%2d-%3d %a%A %2d%%") "01-542 aS 61%" iex> EverFaker.Util.format("%8nBATMAN", n: fn() -> "nana " end) "nana nana nana nana nana nana nana nana BATMAN" """ @spec format(binary, Keyword.t()) :: binary def format( format_str, rules \\ [d: &digit/0, A: &upper_letter/0, a: &lower_letter/0, b: &letter/0] ) do Regex.replace(~r/%(?:%|(\d*)([a-zA-Z]))/, format_str, &format_replace(&1, &2, &3, rules)) end defp format_replace("%%", _, _, _), do: "%" defp format_replace(_, "", rule_char, rules) do format_replace(nil, 1, rule_char, rules) end defp format_replace(_, length_str, rule_char, rules) when is_binary(length_str) do format_replace(nil, String.to_integer(length_str), rule_char, rules) end defp format_replace(_, n, rule_char, rules) when is_integer(n) do rule_key = String.to_existing_atom(rule_char) case rules[rule_key] do fun when is_function(fun) -> join(n, fun) _ -> raise "Rule #{rule_key} not found or not a function" end end end