This commit is contained in:
2025-05-18 15:51:50 +02:00
parent a06a9c4bb8
commit a8128f9fcb
2 changed files with 78 additions and 29 deletions

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3
from sys import stdin
from sys import stdin, stderr
from sys import exit as sysexit
from io import BytesIO
from pathlib import Path
from shutil import get_terminal_size
import click
from blake3 import blake3
@ -16,51 +17,68 @@ BUF_SIZE = 65536 # Linux default pipe capacity is 64KiB (64 * 2^10)
@click.command(
help=(
'Generate OpenSSH style randomart identicon for arbitrary data.\n\n'
'If TEXT or --file is not supplied, data is read from STDIN.'
'If PATHS is not supplied, data is read from STDIN.'
)
)
@click.argument('text', default=None, type=str, required=False)
@click.option(
'--file', '-f', default=None, type=click.Path(exists=True),
help='Calculate from file or directory (recursive).'
)
@click.argument('paths', type=str, nargs=-1)
@click.option(
'--fingerprint', '-p', default=False, required=False, is_flag=True,
help='Print fingerprint instead of identicon.'
)
def main(**kwargs):
if not (stream := get_input_stream(kwargs)):
@click.option(
'--tty', '-t', default=False, required=False, is_flag=True,
help='Read from STDIN even if it is a TTY.'
)
def main(paths, **kwargs):
kwargs['paths'] = paths
if not (streams := get_input_streams(kwargs)):
print_usage_and_exit()
digest = get_digest(stream.stream)
stream.close()
if not kwargs.get('fingerprint'):
i = Identicon(digest)
i.calculate()
print(i)
else:
print(digest.hex())
digests = calculate_digests(streams)
print_output(digests, **kwargs)
def get_input_stream(kwargs):
stream = None
if (text := kwargs['text']) is not None:
stream = ClosableStream(BytesIO(text.encode()))
elif file := kwargs['file']:
stream = get_deterministic_stream(file, BUF_SIZE)
elif not stdin.isatty():
stream = ClosableStream(stdin.buffer)
return stream
def get_input_streams(kwargs):
streams = []
if paths := kwargs['paths']:
streams = get_streams_from(paths)
elif not stdin.isatty() or kwargs['tty']:
streams = [ClosableStream(stdin.buffer)]
return streams
def get_streams_from(paths):
paths = assert_paths_exist(paths)
return [
get_deterministic_stream(path, BUF_SIZE)
for path in paths
]
def assert_paths_exist(paths):
paths = [Path(path) for path in paths]
for path in paths:
if not path.exists():
print(f'No such file or directory: "{path}"', file=stderr)
sysexit(1)
return paths
def print_usage_and_exit():
command = main
with click.Context(command) as ctx:
click.echo(command.get_help(ctx))
click.echo(command.get_help(ctx), err=True)
sysexit(1)
def calculate_digests(streams):
digests = []
for stream in streams:
digests.append(get_digest(stream.stream))
stream.close()
return digests
def get_digest(stream):
# pylint: disable=not-callable
hasher = blake3()
@ -69,6 +87,36 @@ def get_digest(stream):
return hasher.digest(length=DIGEST_SIZE)
def print_output(digests, **kwargs):
if not kwargs.get('fingerprint'):
print_identicons(digests)
else:
print_fingerprints(digests)
def print_identicons(digests):
icons = [Identicon(digest).calculate() for digest in digests]
for i in range(Identicon.HEIGHT+2):
line = ''
for i_icon, icon in enumerate(icons):
if i_icon != 0:
line += ' '
line += str(icon).splitlines()[i]
print(line)
def calculate_spacing(n_icons):
terminal_width = get_terminal_size().columns
icons_width = n_icons * (Identicon.WIDTH+2)
return abs(terminal_width - icons_width) // n_icons
def print_fingerprints(digests):
for digest in digests:
print(digest.hex())
if __name__ == '__main__':
main()

View File

@ -39,6 +39,7 @@ class Identicon:
for command in self._get_commands():
self._execute(command)
self._end_position = self._bishop_position
return self
def _get_commands(self):
commands = []