From c17326f4e7d696e5465edbcca507859cac59f277 Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Mon, 3 Jun 2024 12:20:07 -0700 Subject: [PATCH] Refactor matricizer, fix some linter complaints * Matricizer.choose_dimensions was not actually being used. It has been removed, and dimensions of the hash matrix are now determined internally by the matricizer, rather than in its constructor. * Linter was complaining about missing documentation and some other minor styling things so those are fixed * Linter had a few more stupid lints enabled ("too-many-statements"? really? god forbid your code is too long, I'm not a computer science student, come on guys) Signed-off-by: Alek Ratzloff --- .pylintrc | 4 ++- colorhash/cli.py | 29 +++++++-------- colorhash/matricizer.py | 73 +++++++++++++++++++------------------ colorhash/palettes.py | 1 - colorhash/writer.py | 32 +++++++++++++---- examples/commithash.svg | 80 ++++++++++++++++++++--------------------- 6 files changed, 117 insertions(+), 102 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8a3e7df..cfb67a0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -438,7 +438,9 @@ disable=raw-checker-failed, use-implicit-booleaness-not-comparison-to-zero, too-many-locals, too-few-public-methods, - line-too-long + too-many-statements, + line-too-long, + redefined-builtin, # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/colorhash/cli.py b/colorhash/cli.py index 1958c67..9c613c2 100644 --- a/colorhash/cli.py +++ b/colorhash/cli.py @@ -7,12 +7,10 @@ import textwrap from .color import colorize from .matricizer import Matricizer, NibbleMatricizer, RandomartMatricizer -from .palettes import Palette, DEFAULT_PALETTES, PALETTES +from .palettes import Palette, PALETTES from .writer import ANSIWriter, SVGWriter, Writer -# TODO - WASM compile for embedding directly in HTML -# - this may not be an option, sadly. might have to just port it to JS # TODO - option to add a caption based on the filename (for SVG) # TODO - load palettes from a file # TODO - PNG output @@ -36,11 +34,13 @@ def cli_main() -> None: PALETTE_HELP = "\n".join( [ "PALETTE CHOICES", - '\n'.join(textwrap.wrap( - ", ".join(PALETTE_CHOICES), - initial_indent=" ", - subsequent_indent=" ", - )), + "\n".join( + textwrap.wrap( + ", ".join(PALETTE_CHOICES), + initial_indent=" ", + subsequent_indent=" ", + ) + ), ] ) HASH_CHOICES = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"] @@ -59,7 +59,9 @@ def cli_main() -> None: OUTPUT_TYPE_HELP = "OUTPUT TYPE (-y, --output-type)\n" + "\n".join( [f" {choice} - {desc}" for choice, desc in OUTPUT_TYPE_CHOICES.items()] ) - EPILOGUE = "\n\n".join([MATRIX_HELP, PALETTE_HELP, INPUT_TYPE_HELP, OUTPUT_TYPE_HELP]) + EPILOGUE = "\n\n".join( + [MATRIX_HELP, PALETTE_HELP, INPUT_TYPE_HELP, OUTPUT_TYPE_HELP] + ) progname: str = sys.argv[0] if progname.endswith("__main__.py"): @@ -175,13 +177,9 @@ def cli_main() -> None: matricizer: Matricizer match args.matrix: case "nibble": - w, h = NibbleMatricizer.DIMENSIONS[args.hash] - matricizer = NibbleMatricizer(w, h) + matricizer = NibbleMatricizer() case "randomart": - # 17x9 is what openssh uses - # TODO - allow configuring dimensions, maybe - w, h = RandomartMatricizer.DIMENSIONS[args.hash] - matricizer = RandomartMatricizer(w, h) + matricizer = RandomartMatricizer() case _: assert False, f"invalid args.matrix: {args.matrix}" @@ -210,4 +208,3 @@ def cli_main() -> None: sys.stdout.write(output) else: args.out.write_text(output) - diff --git a/colorhash/matricizer.py b/colorhash/matricizer.py index bc523d2..ce9a1b1 100644 --- a/colorhash/matricizer.py +++ b/colorhash/matricizer.py @@ -1,5 +1,6 @@ "All things that turn a hash into a matrix." import abc +import re from typing import Mapping, Sequence from .palettes import Palette, DEFAULT_PALETTES, GRADIENT_PALETTES, MULTICOLOR_PALETTES @@ -8,6 +9,27 @@ from .palettes import Palette, DEFAULT_PALETTES, GRADIENT_PALETTES, MULTICOLOR_P Matrix = Sequence[Sequence[int]] +def detect_hash_algorithm(hash_or_algo: str | bytes) -> str | None: + dimensions: Mapping[int, str] = { + 32: "md5", + 40: "sha1", + 56: "sha224", + 64: "sha256", + 96: "sha384", + 128: "sha512", + } + if isinstance(hash_or_algo, bytes): + return dimensions.get(len(hash_or_algo) * 2) + + hoa = hash_or_algo.lower() + if re.match(r"^([0-9a-fA-F]{2})+$", hoa): + return dimensions.get(len(hoa)) + elif hoa in list(dimensions.values()): + return hoa + else: + return None + + class Matricizer(metaclass=abc.ABCMeta): """ The base Matricizer class. @@ -16,16 +38,6 @@ class Matricizer(metaclass=abc.ABCMeta): (inclusive). The method by which this is done is up to the matricizer. """ - def __init__(self, w: int, h: int) -> None: - """ - Create a new matricizer for the given dimensions. - - :param w: the width of the output matrix. - :param h: the height of hte output matrix. - """ - self.w = w - self.h = h - @abc.abstractmethod def matricize(self, data: bytes) -> Matrix: """ @@ -35,16 +47,6 @@ class Matricizer(metaclass=abc.ABCMeta): :returns: the matrix converted from the hash data. """ - @staticmethod - @abc.abstractmethod - def choose_dimensions(hash: str) -> tuple[int, int]: - """ - Choose the dimensions for this matrix based on the hash algorithm. - - :param hash: the hash algorithm being used. - :returns: a width and height as a tuple. - """ - def choose_palette( self, data: bytes, palettes: Mapping[str, Palette] | None = None ) -> Palette: @@ -84,32 +86,31 @@ class NibbleMatricizer(Matricizer): :returns: the matrix converted from the hash data. """ + algo = detect_hash_algorithm(data) + w, h = self.DIMENSIONS[algo] + nibbles = [] for b in data: top = (b & 0xF0) >> 4 bottom = b & 0x0F nibbles += [top, bottom] - if len(nibbles) != self.w * self.h: + if len(nibbles) != w * h: raise ValueError( f"input data length ({len(nibbles)}) must match matrix dimensions " - f"({self.w}x{self.h} = {self.w * self.h})" + f"({w}x{h} = {w * h})" ) cols = [] row = [] for b in nibbles: row += [b] - if len(row) == self.w: + if len(row) == w: cols += [row] row = [] return cols - @staticmethod - def choose_dimensions(hash: str) -> tuple[int, int]: - return NibbleMatricizer.DIMENSIONS[hash] - def choose_palette( self, data: bytes, palettes: Mapping[str, Palette] | None = None ) -> Palette: @@ -150,10 +151,12 @@ class RandomartMatricizer(Matricizer): :param data: the hash data to turn into a matrix. :returns: the matrix converted from the hash data. """ + algo = detect_hash_algorithm(data) + w, h = self.DIMENSIONS[algo] - rows = [[0] * self.w for _ in range(self.h)] - c = self.w // 2 - r = self.h // 2 + rows = [[0] * w for _ in range(h)] + c = w // 2 + r = h // 2 for value in data: for _ in range(4): if value & 0x1: @@ -164,18 +167,14 @@ class RandomartMatricizer(Matricizer): r += 1 else: r -= 1 - c = min(max(c, 0), self.w - 1) - r = min(max(r, 0), self.h - 1) + c = min(max(c, 0), w - 1) + r = min(max(r, 0), h - 1) # max value is 0xf if rows[r][c] < 0xF: rows[r][c] += 1 - value >>= 2; + value >>= 2 return rows - @staticmethod - def choose_dimensions(hash: str) -> tuple[int, int]: - return RandomartMatricizer.DIMENSIONS[hash] - def choose_palette( self, data: bytes, palettes: Mapping[str, Palette] | None = None ) -> Palette: diff --git a/colorhash/palettes.py b/colorhash/palettes.py index ae97363..c2b553c 100644 --- a/colorhash/palettes.py +++ b/colorhash/palettes.py @@ -1,5 +1,4 @@ "Base color palette definitions." -import abc from typing import Sequence from .color import Color, HSLColor diff --git a/colorhash/writer.py b/colorhash/writer.py index 8a59b2d..1ad365b 100644 --- a/colorhash/writer.py +++ b/colorhash/writer.py @@ -1,3 +1,4 @@ +"Colorhash writer classes" import abc from .color import Color, ColorMatrix @@ -13,30 +14,47 @@ class Writer(metaclass=abc.ABCMeta): @abc.abstractmethod def write(self, matrix: ColorMatrix) -> str: - "Write the color matrix to a string." + """ + Write the color matrix to a string. + + :param matrix: the color matrix to generate the SVG for. + :returns: the full generated SVG as a string. + """ class ANSIWriter(Writer): + """ + ANSI terminal writer. This will output a 24-bit true color string. + """ def write(self, matrix: ColorMatrix) -> str: - ESC = "\x1b" - RESET = f"{ESC}[0m" - C = "██" + """ + Write the color matrix to an ANSI string. + + :param matrix: the color matrix to generate the SVG for. + :returns: the full generated SVG as a string. + """ + esc = "\x1b" + reset = f"{esc}[0m" + c = "██" def ansi_color(c: Color) -> str: c = c.to_rgb() - return f"{ESC}[38;2;{round(c.r)};{round(c.g)};{round(c.b)}m" + return f"{esc}[38;2;{round(c.r)};{round(c.g)};{round(c.b)}m" out = "" for row in matrix: for col in row: out += ansi_color(col) - out += C + out += c out += "\n" - out += RESET + out += reset return out class SVGWriter(Writer): + """ + SVG string writer. + """ def __init__(self, square_size: int) -> None: """ Create a new SVG writer that uses the given square size. diff --git a/examples/commithash.svg b/examples/commithash.svg index 2ea0503..ef223ed 100644 --- a/examples/commithash.svg +++ b/examples/commithash.svg @@ -1,42 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file