aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-16 16:51:46 +0200
committerMattias Andrée <m@maandree.se>2026-05-16 16:51:46 +0200
commit4f162f357e0ef260b714a51e5fbd0daecb12390c (patch)
tree9c22ed1f6280ab52df9c1e27b670e7234153e370
downloadsumart-4f162f357e0ef260b714a51e5fbd0daecb12390c.tar.gz
sumart-4f162f357e0ef260b714a51e5fbd0daecb12390c.tar.bz2
sumart-4f162f357e0ef260b714a51e5fbd0daecb12390c.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--.gitignore17
-rw-r--r--LICENSE15
-rw-r--r--Makefile37
-rw-r--r--README41
-rw-r--r--config.mk8
-rw-r--r--sumart.179
-rw-r--r--sumart.c215
7 files changed, 412 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ead19db
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*\#*
+*~
+*.o
+*.a
+*.t
+*.lo
+*.to
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/sumart
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1634eae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2026 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..dc89666
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OBJ =\
+ sumart.o
+
+HDR =
+
+all: sumart
+$(OBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+sumart: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LDFLAGS)
+
+install: sumart
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/"
+ cp -- sumart "$(DESTDIR)$(PREFIX)/bin/"
+ cp -- sumart.1 "$(DESTDIR)$(MANPREFIX)/man1/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/sumart"
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/sumart.1"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda
+ -rm -f -- sumart
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..1e3a412
--- /dev/null
+++ b/README
@@ -0,0 +1,41 @@
+NAME
+ sumart - Visualise a checksum
+
+SYNOPSIS
+ sumart [rows columns] [checksum]
+
+DESCRIPTION
+ The sumart utility creates a visualisation of a checksum
+ using symbols and colours in a grid.
+
+ If no checksum is specified, it is read from the standard
+ input.
+
+ Any symbol that is not a hexadecimal digit is silently
+ discarded from the checksum.
+
+OPTIONS
+ The sumart utility conforms to the Base Definitions volume of
+ POSIX.1-2024, Section 12.2, Utility Syntax Guidelines.
+
+ No options are supported.
+
+OPERANDS
+ The following operands are supported:
+
+ rows
+ The number of rows tall the visualisation should be.
+
+ colums
+ The number of columns wide the visualisation should be.
+
+ checksum
+ The checksum to visualise.
+
+EXIT STATUS
+ 0 Successful completion.
+
+ 1 An error occurred.
+
+SEE ALSO
+ None.
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..aeb8cec
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS = -lsimple
diff --git a/sumart.1 b/sumart.1
new file mode 100644
index 0000000..bf50214
--- /dev/null
+++ b/sumart.1
@@ -0,0 +1,79 @@
+.TH SUMART 1 SUMART
+.SH NAME
+sumart \- Visualise a checksum
+
+.SH SYNOPSIS
+.B sumart
+.RI [ rows
+.IR columns ]
+.RI [ checksum ]
+
+.SH DESCRIPTION
+The
+.B sumart
+utility creates a visualisation of a checksum
+using symbols and colours in a grid.
+.PP
+If no
+.I checksum
+is specified, it is read from the standard input.
+.PP
+Any symbol that is not a hexadecimal digit
+is silently discarded from the checksum.
+
+.SH OPTIONS
+The
+.B sumart
+utility conforms to the Base Definitions volume of
+POSIX.1-2024,
+.IR "Section 12.2" ,
+.IR "Utility Syntax Guidelines" .
+.PP
+No options are supported.
+
+.SH OPERANDS
+The following operands are supported:
+.TP
+.I rows
+The number of rows tall the visualisation should be.
+.TP
+.I colums
+The number of columns wide the visualisation should be.
+.TP
+.I checksum
+The checksum to visualise.
+
+.SH STDIN
+The
+.B sumart
+utility reads the checksum from the standard input
+if no checksum is specified.
+
+.SH INPUT FILES
+None.
+
+.SH ENVIRONMENT VARIABLES
+No environment variables affect the execution of sumart.
+
+.SH STDOUT
+The
+.B sumart
+utility prints the visualisation to the
+standard output.
+
+.SH STDERR
+The standard error is only used for diagnostic messages.
+
+.SH OUTPUT FILES
+None.
+
+.SH EXIT STATUS
+.TP
+0
+Successful completion.
+.TP
+1
+An error occurred.
+
+.SH SEE ALSO
+None.
diff --git a/sumart.c b/sumart.c
new file mode 100644
index 0000000..e5ae509
--- /dev/null
+++ b/sumart.c
@@ -0,0 +1,215 @@
+/* See LICENSE file for copyright and license details. */
+#include <libsimple.h>
+#include <libsimple-arg.h>
+
+USAGE("[rows columns] [checksum]");
+
+#define BITS 2
+
+
+int
+main(int argc, char *argv[])
+{
+#if BITS == 5
+ static const ssize_t x_movements[1 << BITS] = {
+ -3, -2, -1, +1, +2, +3,
+ -3, -2, -1, +1, +2, +3,
+ -3, -2, +2, +3,
+ -3, -2, +2, +3,
+ -3, -2, -1, +1, +2, +3,
+ -3, -2, -1, +1, +2, +3,
+ };
+ static const ssize_t y_movements[1 << BITS] = {
+ -3, -3, -3, -3, -3, -3,
+ -2, -2, -2, -2, -2, -2,
+ -1, -1, -1, -1,
+ +1, +1, +1, +1,
+ +2, +2, +2, +2, +2, +2,
+ +3, +3, +3, +3, +3, +3,
+ };
+#elif BITS == 4
+ static const ssize_t x_movements[1 << BITS] = {
+ -2, -1, 0, 0, +1, +2,
+ -2, -1, +1, +2,
+ -2, -1, 0, 0, +1, +2
+ };
+ static const ssize_t y_movements[1 << BITS] = {
+ -2, -1, -2, -1, -2, -1,
+ 0, 0, 0, 0,
+ +1, +2, +1, +2, +1, +2
+ };
+#elif BITS == 3
+ static const ssize_t x_movements[1 << BITS] = {
+ -1, 0, +1,
+ -1, +1,
+ -1, 0, +1
+ };
+ static const ssize_t y_movements[1 << BITS] = {
+ -1, -1, -1,
+ 0, 0,
+ +1, +1, +1
+ };
+#elif BITS == 2
+# if 0
+ static const ssize_t x_movements[1 << BITS] = {
+ -1, +1,
+ -1, +1
+ };
+ static const ssize_t y_movements[1 << BITS] = {
+ -1, -1,
+ +1, +1
+ };
+# else
+ static const ssize_t x_movements[1 << BITS] = {
+ 0, -1,
+ +1, 0
+ };
+ static const ssize_t y_movements[1 << BITS] = {
+ -1, 0,
+ 0, +1
+ };
+# endif
+#endif
+
+ static const char *const symbols[] = {
+ ":", ".", "+", "=",
+ "¤", "o", "*", "£",
+ "X", "%", "&", "B",
+ "@", "#", "/", "^"
+ };
+ static const char *const colours[] = {
+ "", "34;1", "33;1"
+ };
+
+ const char *checksum;
+ char *data = NULL, *end;
+ size_t size = 0u;
+ size_t len = 0u, cell;
+ size_t i, n, x[2], y[2], v, bits;
+ int d, colour;
+ ssize_t r;
+ size_t rows = 15u, cols = 32u;
+ unsigned char *art;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1) {
+ if ('1' > argv[0][0u] || argv[0][0u] > '9' ||
+ '1' > argv[1][0u] || argv[1][0u] > '9')
+ usage();
+
+ errno = 0;
+ rows = strtouz(argv[0], &end, 10);
+ if (!rows || *end || errno)
+ usage();
+ cols = strtouz(argv[1], &end, 10);
+ if (!cols || *end || errno)
+ usage();
+
+ argc -= 2;
+ argv += 2;
+ }
+
+ if (argc > 1)
+ usage();
+
+ if (argc) {
+ checksum = *argv;
+ } else {
+ for (;;) {
+ if (len == size)
+ data = erealloc(data, size += 128u);
+ r = read(STDIN_FILENO, &data[len], size - len);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINVAL)
+ continue;
+ eprintf("read <stdin>:");
+ }
+ n = len + (size_t)r;
+ for (i = len; i < n; i++) {
+ if (('0' <= data[i] && data[i] <= '9') ||
+ ('A' <= data[i] && data[i] <= 'F') ||
+ ('a' <= data[i] && data[i] <= 'f'))
+ data[len++] = data[i];
+ }
+ }
+ data = erealloc(data, len + 1u);
+ data[len] = '\0';
+ checksum = data;
+ }
+
+ art = ecalloc(rows, cols);
+
+ x[0] = x[1] = cols / 2u;
+ y[0] = y[1] = rows / 2u;
+
+ colour = 0;
+ v = 0u;
+ bits = 0u;
+ for (i = 0u; checksum[i]; i++) {
+ if (('0' > checksum[i] || checksum[i] > '9') &&
+ ('A' > checksum[i] || checksum[i] > 'F') &&
+ ('a' > checksum[i] || checksum[i] > 'f'))
+ continue;
+ d = ((int)checksum[i] & 15) + (checksum[i] > '9' ? 9 : 0);
+ v <<= 4;
+ v |= (size_t)d;
+ bits += 4u;
+ while (bits >= BITS) {
+ x[colour] += (size_t)x_movements[v % ELEMSOF(x_movements)];
+ y[colour] += (size_t)y_movements[v % ELEMSOF(y_movements)];
+ x[colour] %= cols;
+ y[colour] %= rows;
+ cell = y[colour] * cols + x[colour];
+ art[cell] = (unsigned char)(art[cell] + (colour ? 0x20u : 1u));
+ art[cell] &= 0xEFu;
+ v >>= BITS;
+ bits -= BITS;
+ if (ELEMSOF(colours) > 1u)
+ colour ^= 1;
+ }
+ }
+ if (bits) {
+ x[colour] += (size_t)x_movements[v % ELEMSOF(x_movements)];
+ y[colour] += (size_t)y_movements[v % ELEMSOF(y_movements)];
+ x[colour] %= cols;
+ y[colour] %= rows;
+ cell = y[colour] * cols + x[colour];
+ art[cell] = (unsigned char)(art[cell] + (colour ? 0x20u : 1u));
+ art[cell] &= 0xEFu;
+ }
+
+ printf("+");
+ for (i = 0u; i < cols; i++)
+ printf("-");
+ printf("+\n");
+ for (*y = 0u; *y < rows; ++*y) {
+ printf("|");
+ for (*x = 0u; *x < cols; ++*x) {
+ cell = *y * cols + *x;
+ if (!art[cell]) {
+ printf(" ");
+ continue;
+ }
+ printf("\033[%sm%s\033[m",
+ colours[(art[cell] / 0x20u) % ELEMSOF(colours)],
+ symbols[(art[cell] & 0x0Fu) % ELEMSOF(symbols)]);
+ }
+ printf("|\n");
+ }
+ printf("+");
+ for (i = 0u; i < cols; i++)
+ printf("-");
+ printf("+\n");
+
+ if (ferror(stdout) || fclose(stdout))
+ eprintf("printf:");
+ free(art);
+ free(data);
+ return 0;
+}