Coverage for kryptic_cypher/cypher/ceasar_word.py: 97%

36 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-20 18:52 +0000

1import string 

2 

3from kryptic_cypher.cypher.base import CypherResult, CypherWithKey, ValidationResult 

4 

5 

6class Direction: 

7 ENCODE = "encode" 

8 DECODE = "decode" 

9 

10 

11def transform_word_into_alphabet(word: str, direction: Direction): 

12 unique_letters_in_order = [] 

13 alphabet = string.ascii_lowercase 

14 for letter in word.lower(): 

15 if letter not in unique_letters_in_order and letter in string.ascii_lowercase: 

16 unique_letters_in_order.append(letter) 

17 alphabet = alphabet.replace(letter, "") 

18 

19 # Shift the letters to the front. 

20 alphabet = "".join(unique_letters_in_order) + alphabet 

21 translation = {} 

22 

23 if direction == Direction.ENCODE: 

24 translation.update(zip(string.ascii_lowercase, alphabet)) 

25 translation.update(zip(string.ascii_uppercase, alphabet.upper())) 

26 

27 elif direction == Direction.DECODE: 

28 translation.update(zip(alphabet, string.ascii_lowercase)) 

29 translation.update(zip(alphabet.upper(), string.ascii_uppercase)) 

30 else: 

31 raise ValueError(f"Invalid direction: {direction}") 

32 

33 return translation 

34 

35 

36class CaesarWordCypher(CypherWithKey): 

37 """ 

38 The caesar word cypher might actually be a misnomer because it isn't really the same as a caesar cypher. in fact, i would argue it would have nothign to do with it. 

39 it involves using a word and taking out the alphabetical order of the letters from that and leaving the other letters of the alphabet that are unused the same. 

40 

41 

42 lets say our key is: Goat 

43 so we would make a new Alphabet starting with the Characters in sequence 

44 G O A T 

45 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 

46 then we check off which ones we took on the actually alphabet line 

47 G O A T 

48 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 

49 x x x x 

50 and we fill in the blanks with the rest of the remaining alphabet in alphabetical order like this. 

51 

52 G O A T B C D E F H I J K L M N P Q R S U V W X Y Z 

53 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 

54 and now you have the the alpahbet to use to change your message 

55 

56 So lets say your message is: alex this is cool. 

57 we look at the regular alphabet for a then we know that should become the letter above it i.e. G 

58 so 

59 G 

60 and then keep going with it 

61 GJBX SEFR FR AMMN 

62 and to decrypt you recreate the alphabet again 

63 and work in reverse i.e. read the top to find the letter on the bottom that matches 

64 so we notice our G becomes an A. 

65 and just repeat through the cyphered text 

66 ALEX THIS IS COOL 

67 then blammo that would be the decrypted text. 

68 """ 

69 

70 @classmethod 

71 def validate_key(cls, key: str) -> ValidationResult: 

72 if not key: 

73 return ValidationResult.fail("Key cannot be empty.") 

74 

75 if any(c not in string.ascii_letters for c in key): 

76 return ValidationResult.fail("Key must be only letters.") 

77 

78 return ValidationResult.ok() 

79 

80 def encode(self, text: str, key: str) -> CypherResult: 

81 translate = transform_word_into_alphabet(key, Direction.ENCODE) 

82 return CypherResult.ok(text, "".join(translate.get(c, c) for c in text)) 

83 

84 def decode(self, text: str, key: str) -> CypherResult: 

85 translate = transform_word_into_alphabet(key, Direction.DECODE) 

86 return CypherResult.ok(text, "".join(translate.get(c, c) for c in text))