"""
Absfuyu: Generator
------------------
This generate stuff (Not python's ``generator``)
Version: 5.1.0
Date updated: 10/03/2025 (dd/mm/yyyy)
Features:
---------
- Generate random string
- Generate key
- Generate check digit
- Generate combinations of list in range
"""
# Module level
# ---------------------------------------------------------------------------
__all__ = ["Charset", "Generator"]
# Library
# ---------------------------------------------------------------------------
import string
from itertools import chain, combinations
from random import choice
from absfuyu.core import BaseClass
from absfuyu.logger import logger
from absfuyu.util import set_max, set_min_max
# Class
# ---------------------------------------------------------------------------
[docs]
class Charset:
"""
Character set data class
"""
DEFAULT = string.ascii_letters + string.digits
ALPHABET = string.ascii_letters
FULL = string.ascii_letters + string.digits + string.punctuation
UPPERCASE = string.ascii_uppercase
LOWERCASE = string.ascii_lowercase
DIGIT = string.digits
SPECIAL = string.punctuation
ALL = string.printable
PRODUCT_KEY = "BCDFGHJKMNPQRTVWXY2346789" # Charset that various key makers use
def __str__(self) -> str:
charset = [x for x in self.__class__.__dict__.keys() if not x.startswith("__")]
return f"List of Character set: {charset}"
def __repr__(self) -> str:
return self.__str__()
[docs]
class Generator(BaseClass):
"""
Generator that generate stuffs
"""
[docs]
@staticmethod
def generate_string(
charset: str = Charset.DEFAULT,
size: int = 8,
times: int = 1,
unique: bool = False,
string_type_if_1: bool = False,
):
"""
Generate a list of random string from character set (Random string generator)
Parameters
----------
charset : str
- Use custom character set or character sets already defined in ``Charset``
- ``Charset.DEFAULT``: character in [a-zA-Z0-9] (default)
- ``Charset.ALPHABET``: character in [a-zA-Z]
- ``Charset.FULL``: character in [a-zA-Z0-9] + special characters
- ``Charset.UPPERCASE``: character in [A-Z]
- ``Charset.LOWERCASE``: character in [a-z]
- ``Charset.DIGIT``: character in [0-9]
- ``Charset.SPECIAL``: character in [!@#$%^&*()_+=-]
- ``Charset.ALL``: character in every printable character
size : int
Length of each string in list
(Default: ``8``)
times : int
How many random string generated
(Default: ``1``)
unique : bool
Each generated text is unique
(Default: ``False``)
string_type_if_1 : bool
Return a ``str`` type result if ``times == 1``
(Default: ``False``)
Returns
-------
list
List of random string generated
str
When ``string_type_if_1`` is ``True``
None
When invalid option
Example:
--------
>>> Generator.generate_string(times=3)
['67Xfh1fv', 'iChcGz9P', 'u82fNzlm']
"""
try:
char_lst = list(charset)
except Exception:
char_lst = charset # type: ignore # ! review this sometime
# logger.debug(char_lst)
unique_string = []
count = 0
logger.debug(f"Unique generated text: {unique}")
while count < times:
s = "".join(choice(char_lst) for _ in range(size))
logger.debug(
f"Time generated: {count + 1}. Remaining: {times - count - 1}. {s}"
)
if not unique:
unique_string.append(s)
count += 1
else:
if s not in unique_string:
unique_string.append(s)
count += 1
logger.debug(unique_string)
if string_type_if_1 and times == 1:
return unique_string[0]
else:
return unique_string
[docs]
@classmethod
def generate_key(
cls,
charset: str = Charset.PRODUCT_KEY,
letter_per_block: int = 5,
number_of_block: int = 5,
sep: str = "-",
) -> str:
"""
Generate custom key
Parameters
----------
charset : str
Character set
(Default: ``Charset.PRODUCT_KEY``)
letter_per_block : int
Number of letter per key block
(Default: ``5``)
number_of_block : int
Number of key block
(Default: ``5``)
sep : str
Key block separator
(Default: ``-``)
Returns
-------
str
Generated key
Example:
--------
>>> Generator.generate_key(letter_per_block=10, number_of_block=2)
'VKKPJVYD2H-M7R687QCV2'
"""
out = sep.join(
cls.generate_string(
charset,
size=letter_per_block,
times=number_of_block,
unique=False,
string_type_if_1=False,
)
)
logger.debug(out)
return out
[docs]
@staticmethod
def generate_check_digit(number: int) -> int:
"""
Check digit generator
"A check digit is a form of redundancy check used for
error detection on identification numbers, such as
bank account numbers, which are used in an application
where they will at least sometimes be input manually.
It is analogous to a binary parity bit used to
check for errors in computer-generated data.
It consists of one or more digits (or letters) computed
by an algorithm from the other digits (or letters) in the sequence input.
With a check digit, one can detect simple errors in
the input of a series of characters (usually digits)
such as a single mistyped digit or some permutations
of two successive digits." (Wikipedia)
This function use Luhn's algorithm to calculate
Parameters
----------
number : int
Number to calculate check digit
Returns
-------
int
Check digit
Example:
--------
>>> Generator.generate_check_digit("4129984562545")
7
"""
logger.debug(f"Base: {number}")
# turn into list then reverse the order
num = list(str(number))[::-1]
sum = 0
logger.debug(f"Reversed: {''.join(num)}")
for i in range(len(num)):
# convert back into integer
num[i] = int(num[i]) # type: ignore
if i % 2 == 0:
# double value of the even-th digit
num[i] *= 2
# sum the character of digit if it's >= 10
if num[i] >= 10: # type: ignore
num[i] -= 9 # type: ignore
sum += num[i] # type: ignore
logger.debug(f"Loop {i + 1}: {num[i]}, {sum}")
out = (10 - (sum % 10)) % 10
logger.debug(f"Output: {out}")
return out
[docs]
@staticmethod
def combinations_range(
sequence: list, *, min_len: int = 1, max_len: int = 0
) -> list[tuple]:
"""
Generate all combinations of a ``sequence`` from ``min_len`` to ``max_len``
Parameters
----------
sequence : list
A sequence that need to generate combination
min_len : int
Minimum ``r`` of ``combinations``
(Default: ``1``)
max_len : int
Maximum ``r`` of ``combinations``
(Default: ``0`` - len of ``sequence``)
Returns
-------
list[tuple]
A list of all combinations from range(``min_len``, ``max_len``) of ``sequence``
Example:
--------
>>> Generator.combinations_range([1, 2, 3], min_len=2)
[(1, 2), (1, 3), (2, 3), (1, 2, 3)]
"""
# Restrain
if max_len < 1:
max_len = len(sequence)
max_len = int(set_max(max_len, max_value=len(sequence)))
min_len = int(set_min_max(min_len, min_value=1, max_value=max_len))
logger.debug(f"Combination range: [{min_len}, {max_len}]")
# Return
return list(
chain.from_iterable(
[list(combinations(sequence, i)) for i in range(min_len, max_len + 1)]
)
)