Break ``ciphertext`` by finding (or using the given) key_period then breaking ``key_period`` many Caesar ciphers.

    Example:
        >>> decryptions = crack("OMSTV", fitness.ChiSquared(analysis.frequency.english.unigrams))
        >>> print(decryptions[0])
        HELLO

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

    Keyword Args:
        key_period (int): The period of the key
        max_key_period (int): The maximum period the key could be

    Returns:
        Sorted list of decryptions

    Raises:
        ValueError: If key_period or max_key_period are less than or equal to 0
        ValueError: If no fitness_functions are given

def crack(ciphertext, *fitness_functions, key_period=None, max_key_period=30):
    """Break ``ciphertext`` by finding (or using the given) key_period then breaking ``key_period`` many Caesar ciphers.

    Example:
        >>> decryptions = crack("OMSTV", fitness.ChiSquared(analysis.frequency.english.unigrams))
        >>> print(decryptions[0])
        HELLO

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

    Keyword Args:
        key_period (int): The period of the key
        max_key_period (int): The maximum period the key could be

    Returns:
        Sorted list of decryptions

    Raises:
        ValueError: If key_period or max_key_period are less than or equal to 0
        ValueError: If no fitness_functions are given
    """
    if max_key_period <= 0 or (key_period is not None and key_period <= 0):
        raise ValueError("Period values must be positive integers")

    original_text = ciphertext
    # Make the assumption that non alphabet characters have not been encrypted
    # TODO: This is fairly poor code. Once languages are a thing, there should be some nice abstractions for this stuff
    ciphertext = remove(ciphertext, string.punctuation + string.whitespace + string.digits)
    periods = [int(key_period)] if key_period else key_periods(ciphertext, max_key_period)

    # Decrypt for every valid period
    period_decryptions = []
    for period in periods:
        if period >= len(ciphertext):
            continue

        # Collect the best decryptions for every column
        column_decryptions = [shift.crack(col, *fitness_functions)[0] for col in split_columns(ciphertext, period)]
        key = _build_key(decrypt.key for decrypt in column_decryptions)

        plaintext = decrypt(key, original_text)
        period_decryptions.append(Decryption(plaintext, key, score(plaintext, *fitness_functions)))

    return sorted(period_decryptions, reverse=True)