# Path: lantern/score.py
# def score(text, *score_functions):
#     """Score ``text`` using ``score_functions``.
# 
#     Examples:
#         >>> score("abc", function_a)
#         >>> score("abc", function_a, function_b)
# 
#     Args:
#         text (str): The text to score
#         *score_functions (variable length argument list): functions to score with
# 
#     Returns:
#         Arithmetic mean of scores
# 
#     Raises:
#         ValueError: If score_functions is empty
#     """
#     if not score_functions:
#         raise ValueError("score_functions must not be empty")
# 
#     return statistics.mean(func(text) for func in score_functions)
# 
# Path: lantern/structures/decryption.py
# class Decryption:
#     """A decryption object, composed of plaintext, a score and the key.
# 
#     Example:
#         >>> decryption = Decryption("example", "key", -10)
#         >>> decryption.plaintext
#         example
#         >>> decryption.key
#         key
#         >>> decryption.score
#         -10
#     """
# 
#     def __init__(self, plaintext, key, score):
#         """
#         Args:
#             plaintext: The decrypted ciphertext
#             key: The key which resulted in this decryption
#             score: The score of this decryption
#         """
#         self.plaintext = plaintext
#         self.key = key
#         self.score = score
# 
#     def __lt__(self, other):
#         """Compare decryptions with other decryptions by score.
# 
#         Args:
#             other: Object to compare with
# 
#         Returns:
#             True if self is less than other, else False
#         """
#         return self.score < other.score


import string
from typing import Callable, Iterable, List
from lantern import score
from lantern.structures import Decryption
          shift_function: ShiftFunction = shift_decrypt_case_english) -> List[Decryption]:
    """Break ``ciphertext`` by enumerating keys between ``min_key`` and ``max_key``.

    Example:
        >>> decryptions = crack("KHOOR", fitness.english.quadgrams)
        >>> print(''.join(decryptions[0].plaintext))
        HELLO

    Args:
        ciphertext (iterable): The symbols to decrypt
        *fitness_functions (variable length argument list): Functions to score decryption with

    Keyword Args:
        min_key (int): Key to start with
        max_key (int): Key to stop at (exclusive)
        shift_function (function(shift, symbol)): Shift function to use

    Returns:
        Sorted list of Decryptions

    Raises:
        ValueError: If min_key exceeds max_key
        ValueError: If no fitness_functions are given
    """
    if min_key >= max_key:
        raise ValueError("min_key cannot exceed max_key")

    decryptions = []
    for key in range(min_key, max_key):
        plaintext = decrypt(key, ciphertext, shift_function)

# Path: lantern/score.py
# def score(text, *score_functions):
#     """Score ``text`` using ``score_functions``.
# 
#     Examples:
#         >>> score("abc", function_a)
#         >>> score("abc", function_a, function_b)
# 
#     Args:
#         text (str): The text to score
#         *score_functions (variable length argument list): functions to score with
# 
#     Returns:
#         Arithmetic mean of scores
# 
#     Raises:
#         ValueError: If score_functions is empty
#     """
#     if not score_functions:
#         raise ValueError("score_functions must not be empty")
# 
#     return statistics.mean(func(text) for func in score_functions)
# 
# Path: lantern/structures/decryption.py
# class Decryption:
#     """A decryption object, composed of plaintext, a score and the key.
# 
#     Example:
#         >>> decryption = Decryption("example", "key", -10)
#         >>> decryption.plaintext
#         example
#         >>> decryption.key
#         key
#         >>> decryption.score
#         -10
#     """
# 
#     def __init__(self, plaintext, key, score):
#         """
#         Args:
#             plaintext: The decrypted ciphertext
#             key: The key which resulted in this decryption
#             score: The score of this decryption
#         """
#         self.plaintext = plaintext
#         self.key = key
#         self.score = score
# 
#     def __lt__(self, other):
#         """Compare decryptions with other decryptions by score.
# 
#         Args:
#             other: Object to compare with
# 
#         Returns:
#             True if self is less than other, else False
#         """
#         return self.score < other.score


# Path: lantern/modules/shift.py
import string
from typing import Callable, Iterable, List
from lantern import score
from lantern.structures import Decryption

          shift_function: ShiftFunction = shift_decrypt_case_english) -> List[Decryption]:
    """Break ``ciphertext`` by enumerating keys between ``min_key`` and ``max_key``.

    Example:
        >>> decryptions = crack("KHOOR", fitness.english.quadgrams)
        >>> print(''.join(decryptions[0].plaintext))
        HELLO

    Args:
        ciphertext (iterable): The symbols to decrypt
        *fitness_functions (variable length argument list): Functions to score decryption with

    Keyword Args:
        min_key (int): Key to start with
        max_key (int): Key to stop at (exclusive)
        shift_function (function(shift, symbol)): Shift function to use

    Returns:
        Sorted list of Decryptions

    Raises:
        ValueError: If min_key exceeds max_key
        ValueError: If no fitness_functions are given
    """
    if min_key >= max_key:
        raise ValueError("min_key cannot exceed max_key")

    decryptions = []
    for key in range(min_key, max_key):
        plaintext = decrypt(key, ciphertext, shift_function)

        decryptions.append(Decryption(plaintext, key, score(plaintext, *fitness_functions)))