-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 => <>, 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 => <>, 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 => <>, 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.