Current section
Files
Jump to
Current section
Files
src/enanoid.erl
-module(enanoid).
-export([generate/0, generate/2]).
%%====================================================================
%% API functions
%%====================================================================
-spec generate() -> bitstring().
generate() ->
generate(<<"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ">>, 21).
-spec generate(bitstring(), integer()) -> bitstring().
generate(Alphabet, Size) when Size > 0 ->
AlpLen = byte_size(Alphabet),
Mask = calc_mask(AlpLen),
Step = calc_step(Mask, Size, AlpLen),
generate_nanoid(Alphabet, Size, Mask, Step, <<"">>).
%%====================================================================
%% Internal functions
%%====================================================================
generate_nanoid(_Alphabet, Size, _Mask, _Step, Acc) when byte_size(Acc) >= Size ->
binary:part(Acc, 0, Size);
generate_nanoid(Alphabet, Size, Mask, Step, Acc) ->
Random = crypto:strong_rand_bytes(Step),
Nanoid = random_string(Alphabet, Random, Mask, Step, Acc),
generate_nanoid(Alphabet, Size, Mask, Step, Nanoid).
random_string(_Alphabet, _Random, _Mask, 0, Acc) ->
Acc;
random_string(Alphabet, Random, Mask, Step, Acc) ->
Idx = binary:at(Random, Step - 1) band Mask,
Append = if byte_size(Alphabet) > Idx ->
binary:part(Alphabet, Idx, 1);
true ->
<<"">>
end,
random_string(Alphabet, Random, Mask, Step - 1, <<Acc/binary, Append/binary>>).
calc_mask(AlpLen) ->
(2 bsl round(math:floor(math:log(AlpLen - 1)/math:log(2)))) - 1.
calc_step(Mask, Size, AlpLen) ->
Step = math:ceil(1.6 * Mask * Size / AlpLen),
round(Step).