Current section
Files
Jump to
Current section
Files
src/friendly_id.erl
-module(friendly_id).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/friendly_id.gleam").
-export([generate/1, encoder_max_int/1, set_generator_predicate_count/2, set_generator_transform_fn/2, set_generator_separator/2, new_generator/0, new_generator_with_words/2, encode_int/2]).
-export_type([encoding_error/0, generator/0, generator_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type encoding_error() :: out_of_bounds.
-opaque generator() :: {generator,
glearray:array(binary()),
glearray:array(binary()),
integer(),
fun((binary()) -> binary()),
binary(),
integer(),
integer()}.
-type generator_error() :: negative_predicate_count.
-file("src/friendly_id.gleam", 29).
-spec take_random_element(glearray:array(DNX)) -> DNX.
take_random_element(Array) ->
Index = begin
_pipe = erlang:tuple_size(Array),
gleam@int:random(_pipe)
end,
Element@1 = case glearray_ffi:get(Array, Index) of
{ok, Element} -> Element;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"friendly_id"/utf8>>,
function => <<"take_random_element"/utf8>>,
line => 34,
value => _assert_fail,
start => 763,
'end' => 822,
pattern_start => 774,
pattern_end => 785})
end,
Element@1.
-file("src/friendly_id.gleam", 38).
-spec prepend_predicates(
list(binary()),
integer(),
integer(),
glearray:array(binary())
) -> list(binary()).
prepend_predicates(Acc, Count, Max, Predicates) ->
case Count =:= Max of
true ->
Acc;
false ->
prepend_predicates(
[take_random_element(Predicates) | Acc],
Count + 1,
Max,
Predicates
)
end.
-file("src/friendly_id.gleam", 18).
?DOC(
" Generates a friendly ID from a Generator record.\n"
"\n"
" # Examples\n"
"\n"
" ## Create a generator with defaults, then generate an ID\n"
"\n"
" ```gleam\n"
" let generator = friendly_id.new_generator()\n"
" echo friendly_id.generate(generator)\n"
" ```\n"
).
-spec generate(generator()) -> binary().
generate(Generator) ->
_pipe = [take_random_element(erlang:element(2, Generator))],
_pipe@1 = prepend_predicates(
_pipe,
0,
erlang:element(4, Generator),
erlang:element(3, Generator)
),
_pipe@2 = gleam@list:map(_pipe@1, erlang:element(5, Generator)),
gleam@string:join(_pipe@2, erlang:element(6, Generator)).
-file("src/friendly_id.gleam", 149).
-spec extract_raw_indices(integer(), integer(), integer(), list(integer())) -> list(integer()).
extract_raw_indices(Remaining_id, Pred_count, Count_left, Acc) ->
case Count_left =< 0 of
true ->
lists:reverse(Acc);
false ->
Pred_index = case Pred_count of
0 -> 0;
Gleam@denominator -> Remaining_id rem Gleam@denominator
end,
Next_remaining = case Pred_count of
0 -> 0;
Gleam@denominator@1 -> Remaining_id div Gleam@denominator@1
end,
extract_raw_indices(
Next_remaining,
Pred_count,
Count_left - 1,
[Pred_index | Acc]
)
end.
-file("src/friendly_id.gleam", 173).
-spec int_power_loop(integer(), integer(), integer()) -> integer().
int_power_loop(Base, Exponent, Acc) ->
case Exponent =< 0 of
true ->
Acc;
false ->
int_power_loop(Base, Exponent - 1, Acc * Base)
end.
-file("src/friendly_id.gleam", 169).
-spec int_power(integer(), integer()) -> integer().
int_power(Base, Exponent) ->
int_power_loop(Base, Exponent, 1).
-file("src/friendly_id.gleam", 139).
-spec encoder_max_int(generator()) -> integer().
encoder_max_int(Generator) ->
Obj_count = erlang:tuple_size(erlang:element(2, Generator)),
Pred_count = erlang:tuple_size(erlang:element(3, Generator)),
Total_combinations = Obj_count * int_power(
Pred_count,
erlang:element(4, Generator)
),
Total_combinations - 1.
-file("src/friendly_id.gleam", 256).
-spec set_generator_predicate_count(generator(), integer()) -> {ok, generator()} |
{error, generator_error()}.
set_generator_predicate_count(Generator, Predicate_count) ->
case Predicate_count >= 0 of
true ->
{ok,
{generator,
erlang:element(2, Generator),
erlang:element(3, Generator),
Predicate_count,
erlang:element(5, Generator),
erlang:element(6, Generator),
erlang:element(7, Generator),
erlang:element(8, Generator)}};
false ->
{error, negative_predicate_count}
end.
-file("src/friendly_id.gleam", 266).
-spec set_generator_transform_fn(generator(), fun((binary()) -> binary())) -> generator().
set_generator_transform_fn(Generator, Transform_fn) ->
{generator,
erlang:element(2, Generator),
erlang:element(3, Generator),
erlang:element(4, Generator),
Transform_fn,
erlang:element(6, Generator),
erlang:element(7, Generator),
erlang:element(8, Generator)}.
-file("src/friendly_id.gleam", 273).
-spec set_generator_separator(generator(), binary()) -> generator().
set_generator_separator(Generator, Separator) ->
{generator,
erlang:element(2, Generator),
erlang:element(3, Generator),
erlang:element(4, Generator),
erlang:element(5, Generator),
Separator,
erlang:element(7, Generator),
erlang:element(8, Generator)}.
-file("src/friendly_id.gleam", 277).
-spec gcd(integer(), integer()) -> integer().
gcd(A, B) ->
case B =:= 0 of
true ->
A;
false ->
gcd(B, case B of
0 -> 0;
Gleam@denominator -> A rem Gleam@denominator
end)
end.
-file("src/friendly_id.gleam", 284).
-spec find_coprime(integer(), integer()) -> integer().
find_coprime(Length, Candidate) ->
case gcd(Length, Candidate) =:= 1 of
true ->
Candidate;
false ->
find_coprime(Length, Candidate + 1)
end.
-file("src/friendly_id.gleam", 211).
?DOC(
" Create a `Generator` record with the following defaults:\n"
" - Predicate count: 1\n"
" - No separator\n"
" - No transformation\n"
" - Provided word lists\n"
" \n"
" # Examples\n"
" \n"
" ```gleam\n"
" let generator = friendly_id.new_generator()\n"
" ```\n"
).
-spec new_generator() -> generator().
new_generator() ->
Objects = friendly_id@words:get_objects(),
Predicates = friendly_id@words:get_predicates(),
Obj_multiplier = find_coprime(erlang:tuple_size(Objects), 859),
Pred_multiplier = find_coprime(erlang:tuple_size(Predicates), 859),
{generator,
Objects,
Predicates,
1,
fun gleam@function:identity/1,
<<""/utf8>>,
Obj_multiplier,
Pred_multiplier}.
-file("src/friendly_id.gleam", 239).
?DOC(
" Create a `Generator` record with the word lists passed to it and the following defaults:\n"
" - Predicate count: 1\n"
" - No separator\n"
" - No transformation\n"
" \n"
" # Examples\n"
" \n"
" ```gleam\n"
" let objects = glearray.from_list([\"apple, potato\"])\n"
" let predicates = glearray.from_list([\"brave, insightful\"])\n"
" let generator = friendly_id.new_generator_with_words(objects:, predicates:)\n"
" ```\n"
).
-spec new_generator_with_words(
glearray:array(binary()),
glearray:array(binary())
) -> generator().
new_generator_with_words(Objects, Predicates) ->
Obj_multiplier = find_coprime(erlang:tuple_size(Objects), 859),
Pred_multiplier = find_coprime(erlang:tuple_size(Predicates), 859),
{generator,
Objects,
Predicates,
1,
fun gleam@function:identity/1,
<<""/utf8>>,
Obj_multiplier,
Pred_multiplier}.
-file("src/friendly_id.gleam", 74).
?DOC(
" Encodes an int to a Friendly ID. This is a deterministic operation, which means an int will always return the same ID, and guarantees no collisions.\n"
"\n"
" # Examples\n"
"\n"
" ## Create a generator with defaults, then generate an ID by encoding an int\n"
"\n"
" ```gleam\n"
" let generator = friendly_id.new_generator()\n"
" echo friendly_id.encode_int(generator, 23)\n"
" ```\n"
).
-spec encode_int(generator(), integer()) -> {ok, binary()} |
{error, encoding_error()}.
encode_int(Generator, Id) ->
Max_id = encoder_max_int(Generator),
case (Id < 0) orelse (Id > Max_id) of
true ->
{error, out_of_bounds};
false ->
Objects = erlang:element(2, Generator),
Predicates = erlang:element(3, Generator),
Obj_count = erlang:tuple_size(Objects),
Pred_count = erlang:tuple_size(Predicates),
Raw_obj = case Obj_count of
0 -> 0;
Gleam@denominator -> Id rem Gleam@denominator
end,
Remaining_id = case Obj_count of
0 -> 0;
Gleam@denominator@1 -> Id div Gleam@denominator@1
end,
Raw_preds = extract_raw_indices(
Remaining_id,
Pred_count,
erlang:element(4, Generator),
[]
),
Scrambled_obj = case Obj_count of
0 -> 0;
Gleam@denominator@2 -> ((Raw_obj * erlang:element(7, Generator))
+ 1337)
rem Gleam@denominator@2
end,
{_, Scrambled_preds} = gleam@list:map_fold(
Raw_preds,
Scrambled_obj,
fun(Cascade, Raw_pred) ->
Scrambled_pred = case Pred_count of
0 -> 0;
Gleam@denominator@3 -> (((Raw_pred * erlang:element(
8,
Generator
))
+ 1337)
+ Cascade)
rem Gleam@denominator@3
end,
{Scrambled_pred, Scrambled_pred}
end
),
Object_word@1 = case glearray_ffi:get(Objects, Scrambled_obj) of
{ok, Object_word} -> Object_word;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"friendly_id"/utf8>>,
function => <<"encode_int"/utf8>>,
line => 122,
value => _assert_fail,
start => 2804,
'end' => 2869,
pattern_start => 2815,
pattern_end => 2830})
end,
Pred_words = gleam@list:map(
lists:reverse(Scrambled_preds),
fun(Idx) ->
Pred_word@1 = case glearray_ffi:get(Predicates, Idx) of
{ok, Pred_word} -> Pred_word;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"friendly_id"/utf8>>,
function => <<"encode_int"/utf8>>,
line => 126,
value => _assert_fail@1,
start => 2962,
'end' => 3018,
pattern_start => 2973,
pattern_end => 2986})
end,
Pred_word@1
end
),
Parts = lists:append(Pred_words, [Object_word@1]),
Transformed_parts = gleam@list:map(
Parts,
erlang:element(5, Generator)
),
{ok,
gleam@string:join(
Transformed_parts,
erlang:element(6, Generator)
)}
end.