Refactor to unified caesar() function with encode/decode support
- Replace separate encrypt/decrypt with shared caesar() function - Add direction constants (DIRECTION_ENCODE, DIRECTION_DECODE) and prompts - Update main.py to support interactive encode/decode loop - Keep encrypt/decrypt as wrappers for backward compatibility - Add tests for caesar() with both directions and error handling - Update tests for main.py with interactive scenario tests
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
## @file __init__.py
|
||||
# @brief Public API of the caesar_cipher package.
|
||||
|
||||
from caesar_cipher.cipher import decrypt, encrypt
|
||||
from caesar_cipher.cipher import caesar, decrypt, encrypt
|
||||
|
||||
__all__ = ["encrypt", "decrypt"]
|
||||
__all__ = ["caesar", "encrypt", "decrypt"]
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
## @file cipher.py
|
||||
# @brief Caesar cipher encryption and decryption.
|
||||
# @brief Caesar cipher: one shared function for encoding and decoding.
|
||||
|
||||
from caesar_cipher.constants import ALPHABET
|
||||
from caesar_cipher.constants import ALPHABET, DIRECTION_DECODE, DIRECTION_ENCODE
|
||||
|
||||
|
||||
def encrypt(text: str, shift: int) -> str:
|
||||
## @brief Encrypt a text with the Caesar cipher.
|
||||
# @param text Original text.
|
||||
# @param shift Positions to move each letter (may be negative or > 26).
|
||||
# @return The encrypted text; non-letters are kept, case is preserved.
|
||||
def caesar(text: str, shift: int, direction: str) -> str:
|
||||
## @brief Encode or decode a text with the Caesar cipher.
|
||||
# @param text Text to transform.
|
||||
# @param shift Positions to move each letter (may be negative or > 26).
|
||||
# @param direction DIRECTION_ENCODE or DIRECTION_DECODE.
|
||||
# @return The transformed text; non-letters are kept, case is preserved.
|
||||
# @throws ValueError If direction is not a known direction.
|
||||
if direction == DIRECTION_DECODE:
|
||||
shift = -shift
|
||||
elif direction != DIRECTION_ENCODE:
|
||||
raise ValueError(f"Unknown direction: {direction!r}")
|
||||
|
||||
result = []
|
||||
for char in text:
|
||||
lower = char.lower()
|
||||
@@ -21,9 +28,17 @@ def encrypt(text: str, shift: int) -> str:
|
||||
return "".join(result)
|
||||
|
||||
|
||||
def encrypt(text: str, shift: int) -> str:
|
||||
## @brief Encrypt a text with the Caesar cipher.
|
||||
# @param text Original text.
|
||||
# @param shift Number of positions to shift forward.
|
||||
# @return The encrypted text.
|
||||
return caesar(text, shift, DIRECTION_ENCODE)
|
||||
|
||||
|
||||
def decrypt(text: str, shift: int) -> str:
|
||||
## @brief Decrypt a Caesar-cipher text.
|
||||
# @param text Encrypted text.
|
||||
# @param shift Shift that was used to encrypt.
|
||||
# @return The original text.
|
||||
return encrypt(text, -shift)
|
||||
return caesar(text, shift, DIRECTION_DECODE)
|
||||
|
||||
@@ -4,10 +4,38 @@
|
||||
## Lowercase alphabet used to look up letter positions.
|
||||
ALPHABET = list("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
## Direction keyword for encoding.
|
||||
DIRECTION_ENCODE = "encode"
|
||||
## Direction keyword for decoding.
|
||||
DIRECTION_DECODE = "decode"
|
||||
|
||||
## Prompt for the direction.
|
||||
PROMPT_DIRECTION = "Type 'encode' to encrypt, type 'decode' to decrypt:\n"
|
||||
## Prompt for the message.
|
||||
PROMPT_TEXT = "Type your message:\n"
|
||||
## Prompt for the shift number.
|
||||
PROMPT_SHIFT = "Type the shift number:\n"
|
||||
## Prompt to continue.
|
||||
PROMPT_AGAIN = "Type 'yes' if you want to go again. Otherwise type 'no'.\n"
|
||||
|
||||
## Name of the environment file holding tokens.
|
||||
ENV_FILE = ".env"
|
||||
|
||||
## ASCII art title shown at start-up.
|
||||
TITLE = r"""
|
||||
____ _ _____ ____ _ ____
|
||||
/ ___| / \ | ____/ ___| / \ | _ \
|
||||
| | / _ \ | _| \___ \ / _ \ | |_) |
|
||||
| |___ / ___ \| |___ ___) / ___ \| _ <
|
||||
\____/_/ \_\_____|____/_/ \_\_| \_\
|
||||
____ ___ ____ _ _ _____ ____
|
||||
/ ___|_ _| _ \| | | | ____| _ \
|
||||
| | | || |_) | |_| | _| | |_) |
|
||||
| |___ | || __/| _ | |___| _ <
|
||||
\____|___|_| |_| |_|_____|_| \_\
|
||||
"""
|
||||
|
||||
## ANSI escape code for bright cyan text.
|
||||
COLOR_TITLE = "\033[96m"
|
||||
## ANSI escape code that resets text colour.
|
||||
COLOR_RESET = "\033[0m"
|
||||
|
||||
+43
-20
@@ -1,20 +1,43 @@
|
||||
## @file main.py
|
||||
# @brief Interactive command line interface.
|
||||
|
||||
from caesar_cipher import constants as c
|
||||
from caesar_cipher.cipher import encrypt
|
||||
|
||||
|
||||
def main() -> None:
|
||||
## @brief Ask for a message and a shift, then print the encrypted text.
|
||||
text = input(c.PROMPT_TEXT)
|
||||
try:
|
||||
shift = int(input(c.PROMPT_SHIFT))
|
||||
except ValueError:
|
||||
print("The shift must be a whole number.")
|
||||
return
|
||||
print(f"Here's the encoded result: {encrypt(text, shift)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
## @file main.py
|
||||
# @brief Interactive command line interface.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from caesar_cipher import constants as c
|
||||
from caesar_cipher.cipher import caesar
|
||||
|
||||
|
||||
def print_title() -> None:
|
||||
## @brief Print the ASCII title, in colour when the terminal supports it.
|
||||
if sys.stdout.isatty() and "NO_COLOR" not in os.environ:
|
||||
os.system("") # enables ANSI escape sequences on older Windows consoles
|
||||
print(f"{c.COLOR_TITLE}{c.TITLE}{c.COLOR_RESET}")
|
||||
else:
|
||||
print(c.TITLE)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
## @brief Run the interactive encode/decode loop until the user stops.
|
||||
print_title()
|
||||
while True:
|
||||
direction = input(c.PROMPT_DIRECTION).strip().lower()
|
||||
if direction not in (c.DIRECTION_ENCODE, c.DIRECTION_DECODE):
|
||||
print("Please type 'encode' or 'decode'.")
|
||||
continue
|
||||
text = input(c.PROMPT_TEXT)
|
||||
try:
|
||||
shift = int(input(c.PROMPT_SHIFT))
|
||||
except ValueError:
|
||||
print("The shift must be a whole number.")
|
||||
continue
|
||||
|
||||
print(f"Here's the {direction}d result: {caesar(text, shift, direction)}")
|
||||
|
||||
if input(c.PROMPT_AGAIN).strip().lower() != "yes":
|
||||
print("Goodbye")
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user