| 13 | ################################################################################ |
| 14 | |
| 15 | class Key(object): |
| 16 | |
| 17 | slots('data, prefix_len, base, size, encoder, axes, order, decoder') |
| 18 | |
| 19 | @classmethod |
| 20 | def new(cls, chars_used, chain_size): |
| 21 | selection, blocks = list(set(chars_used)), [] |
| 22 | for _ in range(chain_size): |
| 23 | _CHAOS.shuffle(selection) |
| 24 | blocks.append(''.join(selection)) |
| 25 | return cls(tuple(blocks)) |
| 26 | |
| 27 | def __init__(self, data): |
| 28 | self.__test_data(data) |
| 29 | self.__make_vars(data) |
| 30 | |
| 31 | @staticmethod |
| 32 | def __test_data(data): |
| 33 | if not isinstance(data, tuple): |
| 34 | raise TypeError('Data must be a tuple object!') |
| 35 | if len(data) < 2: |
| 36 | raise ValueError('Data must contain at least two items!') |
| 37 | item = data[0] |
| 38 | if not isinstance(item, str): |
| 39 | raise TypeError('Data items must be str objects!') |
| 40 | length = len(item) |
| 41 | if length < 2: |
| 42 | raise ValueError('Data items must contain at least two chars!') |
| 43 | unique = set(item) |
| 44 | if len(unique) != length: |
| 45 | raise ValueError('Data items must contain unique char sets!') |
| 46 | for item in data[1:]: |
| 47 | if not isinstance(item, str): |
| 48 | raise TypeError('Data items must be str objects!') |
| 49 | next_length = len(item) |
| 50 | if next_length != length: |
| 51 | raise ValueError('All data items must have the same size!') |
| 52 | next_unique = set(item) |
| 53 | if len(next_unique) != next_length: |
| 54 | raise ValueError('Data items must contain unique char sets!') |
| 55 | if next_unique ^ unique: |
| 56 | raise ValueError('All data items must use the same char set!') |
| 57 | |
| 58 | def __make_vars(self, data): |
| 59 | self.__data = data |
| 60 | self.__prefix_len = len(data) - 1 |
| 61 | self.__base = base = data[0] |
| 62 | self.__size = size = len(base) |
| 63 | offset = -sum(base.index(block[0]) for block in data[1:-1]) % size |
| 64 | self.__encoder = base[offset:] + base[:offset] |
| 65 | self.__axes = tuple(reversed([tuple(base.index(char) for char in block) |
| 66 | for block in data[1:]])) |
| 67 | self.__order = key = ''.join(sorted(base)) |
| 68 | grid = [] |
| 69 | for rotation in range(size): |
| 70 | block, row = base[rotation:] + base[:rotation], [None] * size |
| 71 | for char, value in zip(block, key): |
| 72 | row[key.index(char)] = value |
no test coverage detected