Implement OpenSSH style identicon calculation
This commit is contained in:
commit
0dd22bd7bc
1
identicon/__init__.py
Normal file
1
identicon/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .identicon import Identicon
|
89
identicon/identicon.py
Normal file
89
identicon/identicon.py
Normal file
@ -0,0 +1,89 @@
|
||||
from dataclasses import dataclass
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
@dataclass
|
||||
class Coordinate:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(
|
||||
self.x+other.x,
|
||||
self.y+other.y
|
||||
)
|
||||
|
||||
|
||||
class Identicon:
|
||||
WIDTH = 17
|
||||
HEIGHT = 9
|
||||
SYMBOLS = ' .o+=*BOX@%&#/^'
|
||||
START_SYMBOL = 'S'
|
||||
END_SYMBOL = 'E'
|
||||
BISHOP_START = Coordinate(8, 4)
|
||||
|
||||
MOVES = {
|
||||
0: Coordinate(-1, -1), # 00 ↖
|
||||
1: Coordinate( 1, -1), # 01 ↗
|
||||
2: Coordinate(-1, 1), # 10 ↙
|
||||
3: Coordinate( 1, 1), # 11 ↘
|
||||
}
|
||||
|
||||
def __init__(self, data):
|
||||
self._data = data
|
||||
self._grid = [[0] * self.WIDTH for _ in range(self.HEIGHT)]
|
||||
self._bishop_position = deepcopy(self.BISHOP_START)
|
||||
self._end_position = None
|
||||
|
||||
def calculate(self):
|
||||
for command in self._get_commands():
|
||||
self._execute(command)
|
||||
self._end_position = self._bishop_position
|
||||
|
||||
def _get_commands(self):
|
||||
commands = []
|
||||
for octet in self._data:
|
||||
commands.extend([
|
||||
octet & 0b11, # 00 00 00 [00]
|
||||
(octet >> 2) & 0b11, # 00 00 [00] 00
|
||||
(octet >> 4) & 0b11, # 00 [00] 00 00
|
||||
(octet >> 6) & 0b11, # [00] 00 00 00
|
||||
])
|
||||
return commands
|
||||
|
||||
def _execute(self, command):
|
||||
move = self.MOVES[command]
|
||||
self._bishop_position = self._ensure_within_grid_borders(self._bishop_position + move)
|
||||
self._increment_current_position()
|
||||
|
||||
def _ensure_within_grid_borders(self, coordinate: Coordinate):
|
||||
coordinate.x = max(0, min(self.WIDTH-1, coordinate.x))
|
||||
coordinate.y = max(0, min(self.HEIGHT-1, coordinate.y))
|
||||
return coordinate
|
||||
|
||||
def _increment_current_position(self):
|
||||
self._grid[self._bishop_position.y][self._bishop_position.x] += 1
|
||||
|
||||
def __str__(self):
|
||||
icon = self._header+'\n'
|
||||
for y, row in enumerate(self._grid):
|
||||
icon += '|'
|
||||
for x, cell in enumerate(row):
|
||||
icon += self._determine_symbol(Coordinate(x, y), cell)
|
||||
icon += '|\n'
|
||||
icon += self._header
|
||||
|
||||
return icon
|
||||
|
||||
@property
|
||||
def _header(self):
|
||||
return f'+{self.WIDTH*"-"}+'
|
||||
|
||||
def _determine_symbol(self, coordinate, cell):
|
||||
symbol = self.SYMBOLS[min(cell, len(self.SYMBOLS)-1)]
|
||||
if coordinate == self.BISHOP_START:
|
||||
symbol = self.START_SYMBOL
|
||||
elif coordinate == self._end_position:
|
||||
symbol = self.END_SYMBOL
|
||||
|
||||
return symbol
|
89
identicon/test_identicon.py
Normal file
89
identicon/test_identicon.py
Normal file
@ -0,0 +1,89 @@
|
||||
from base64 import b64decode
|
||||
from dataclasses import dataclass
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from .identicon import Identicon
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReferenceIcon:
|
||||
fingerprint: bytes
|
||||
icon: str
|
||||
|
||||
|
||||
REFERENCE_ICONS = [
|
||||
ReferenceIcon(
|
||||
bytearray.fromhex(''.join('b7:a3:e2:ce:09:06:ad:39:c8:1d:ad:b5:95:48:8f:99'.split(':'))),
|
||||
dedent('''\
|
||||
+-----------------+
|
||||
| |
|
||||
| |
|
||||
| . |
|
||||
| .o * . |
|
||||
| ...E +S . |
|
||||
|...++ o . . |
|
||||
|..+oo. o |
|
||||
| o o.. . . |
|
||||
| o=.. |
|
||||
+-----------------+'''
|
||||
)
|
||||
),
|
||||
ReferenceIcon(
|
||||
b64decode('sX58LC41tVlsctG1+H5PrkbMDfG374yghEg96KlnFZA=='),
|
||||
dedent('''\
|
||||
+-----------------+
|
||||
| . .o|
|
||||
| E + o|
|
||||
| .. o = |
|
||||
| o.o o B o|
|
||||
| o S. . X +o|
|
||||
| o +.+o.o =..|
|
||||
| +.o.=.+. .+|
|
||||
| .o .+ + ..=+|
|
||||
| .o .o .oo=|
|
||||
+-----------------+'''
|
||||
)
|
||||
),
|
||||
ReferenceIcon(
|
||||
bytearray.fromhex(''.join('9b:cf:42:fd:25:ff:ce:83:e9:e0:f1:d4:10:c3:ae:a8'.split(':'))),
|
||||
dedent('''\
|
||||
+-----------------+
|
||||
| |
|
||||
| . |
|
||||
| + |
|
||||
| . o |
|
||||
| S. o |
|
||||
| .oo o + |
|
||||
| .o. = =o. |
|
||||
| oo. *o.o |
|
||||
| E .o..o o=|
|
||||
+-----------------+'''
|
||||
)
|
||||
),
|
||||
ReferenceIcon(
|
||||
b64decode('5/WozN6loc0G8DxxrJhV8+aG/6Dvz1/gTBVipWU9nb0='),
|
||||
dedent('''\
|
||||
+-----------------+
|
||||
| o.==|
|
||||
| + =o=|
|
||||
| o + +|
|
||||
| . o o oE |
|
||||
| SB.+.+o |
|
||||
| oo*..*o. |
|
||||
| .ooo* .|
|
||||
| o *.=.o.|
|
||||
| .*.*ooo*|
|
||||
+-----------------+'''
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reference", REFERENCE_ICONS)
|
||||
def test_identicon(reference):
|
||||
icon = Identicon(reference.fingerprint)
|
||||
icon.calculate()
|
||||
|
||||
assert str(icon) == reference.icon
|
Loading…
Reference in New Issue
Block a user