aboutsummaryrefslogtreecommitdiffstats
path: root/check
diff options
context:
space:
mode:
Diffstat (limited to 'check')
-rw-r--r--check/check-icon-listing.c398
-rwxr-xr-xcheck/check-icons-listing89
-rwxr-xr-xcheck/find-duplicates9
3 files changed, 407 insertions, 89 deletions
diff --git a/check/check-icon-listing.c b/check/check-icon-listing.c
new file mode 100644
index 0000000..af9eb1f
--- /dev/null
+++ b/check/check-icon-listing.c
@@ -0,0 +1,398 @@
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+enum filetype {
+ ERROR,
+ NO_SUCH_FILE,
+ REGULAR,
+ SYMLINK,
+ OTHER_TYPE
+};
+
+static size_t lineno = 0;
+static char *text = NULL;
+static size_t textlen = 0;
+static int exitstatus = 0;
+static char *filebuf = NULL;
+static char *targetbuf = NULL;
+static char *relbuf = NULL;
+
+static void
+loadicons(void)
+{
+ int fd;
+ size_t size = 0;
+ ssize_t r;
+
+ fd = open("icons.mk", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open icons.mk: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ for (;;) {
+ if (textlen == size) {
+ size += 128UL << 10;
+ text = realloc(text, size);
+ if (!text) {
+ fprintf(stderr, "Failed to allocate enough memory to load icons.mk\n");
+ exit(1);
+ }
+ }
+
+ r = read(fd, &text[textlen], size - textlen);
+ if (r < 0) {
+ fprintf(stderr, "Failed to read icons.mk: %s\n", strerror(errno));
+ exit(1);
+ } else if (!r) {
+ break;
+ } else {
+ textlen += (size_t)r;
+ }
+ }
+
+ close(fd);
+}
+
+static char *
+nextline(void)
+{
+ static size_t pos = 0;
+ char *line = &text[pos];
+
+ if (pos == textlen)
+ return NULL;
+
+ lineno += 1;
+
+ for (; pos < textlen; pos++) {
+ if (!text[pos]) {
+ fprintf(stderr, "Line %zu in icons.mk contains NUL byte\n", lineno);
+ exitstatus = 1;
+ text[pos] = '^';
+ } else if (text[pos] == '\n') {
+ text[pos++] = '\0';
+ break;
+ }
+ }
+
+ return line;
+}
+
+static int
+iscomment(char *line)
+{
+ while (*line == ' ' || *line == '\t')
+ line++;
+ return *line == '#' || !*line;
+}
+
+static void
+remove_line_continuation(char *line)
+{
+ size_t n = strlen(line);
+ if (n && line[n - 1] == '\\')
+ line[n - 1] = '\0';
+}
+
+static void
+rstrip(char *line)
+{
+ size_t n = strlen(line);
+ while (n && (line[n - 1] == ' ' || line[n - 1] == '\t'))
+ line[--n] = '\0';
+}
+
+static size_t
+getindent(char **linep)
+{
+ size_t indent = 0;
+ char *line = *linep;
+ int warned_sp = 0;
+
+ for (;; line++) {
+ if (*line == ' ') {
+ if (!warned_sp) {
+ fprintf(stderr, "Line %zu in icons.mk contains SP character instead of HT\n", lineno);
+ warned_sp = 1;
+ }
+ exitstatus = 1;
+ indent += 1;
+ } else if (*line == '\t') {
+ indent += 8;
+ } else {
+ break;
+ }
+ }
+
+ *linep = line;
+ return (indent + 7) / 8;
+}
+
+static char *
+getfile(char *icon)
+{
+ static size_t size = 0;
+ size_t req = strlen(icon) + sizeof("scalable/.svg");
+ if (req > size) {
+ size = req;
+ filebuf = realloc(filebuf, size);
+ if (!filebuf) {
+ fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n");
+ exit(1);
+ }
+ }
+ stpcpy(stpcpy(stpcpy(filebuf, "scalable/"), icon), ".svg");
+ return filebuf;
+}
+
+static enum filetype
+getlink(char *file, char **target_out)
+{
+ static size_t size = 0;
+ ssize_t r;
+ struct stat st;
+
+ if (!size) {
+ grow_and_try_again:
+ size += 4096;
+ targetbuf = realloc(targetbuf, size);
+ if (!targetbuf) {
+ fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n");
+ exit(1);
+ }
+ }
+
+ r = readlink(file, targetbuf, size - 1);
+ if (r >= 0) {
+ if (r >= size - 2)
+ goto grow_and_try_again;
+ while (r && !targetbuf[r - 1])
+ r -= 1;
+ targetbuf[r] = '\0';
+ *target_out = targetbuf;
+ return SYMLINK;
+ } else if (errno == ENOENT) {
+ *target_out = NULL;
+ return NO_SUCH_FILE;
+ } else if (errno == EINVAL) {
+ *target_out = NULL;
+ if (!lstat(file, &st))
+ return S_ISREG(st.st_mode) ? REGULAR : OTHER_TYPE;
+ fprintf(stderr, "Failure at line %zu in icons.mk: lstat %s: %s\n", lineno, file, strerror(errno));
+ exitstatus = 1;
+ return ERROR;
+ } else {
+ fprintf(stderr, "Failure at line %zu in icons.mk: readlink %s: %s\n", lineno, file, strerror(errno));
+ exitstatus = 1;
+ *target_out = NULL;
+ return ERROR;
+ }
+}
+
+static char *
+rel(char *to, char *from)
+{
+ static size_t size = 0;
+ size_t i, req, up = 0;
+ char *p;
+
+ size_t off = 0;
+ for (i = 0; to[i]; i++) {
+ if (to[i] != from[i])
+ break;
+ if (to[i] == '/')
+ off = i + 1;
+ }
+ to = &to[off];
+ from = &from[off];
+
+ while ((from = strchr(from, '/'))) {
+ from = &from[1];
+ up += 1;
+ }
+
+ if (!up)
+ return to;
+
+ req = up * 3 + strlen(to) + 1;
+ if (req > size) {
+ size += 4096;
+ relbuf = realloc(relbuf, size = req);
+ if (!relbuf) {
+ fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n");
+ exit(1);
+ }
+ }
+
+ p = relbuf;
+ while (up--)
+ p = stpcpy(p, "../");
+ stpcpy(p, to);
+ return relbuf;
+}
+
+int
+main(void)
+{
+ char *line;
+ char *file;
+ char *target;
+ char *goal;
+ int ret = 0;
+ size_t count = 0;
+ size_t indent;
+ char **stack = NULL;
+ size_t stacksize = 0;
+ size_t stacklen = 0;
+ struct stat st1, st2;
+ const char *diff;
+ size_t len;
+ int fixed;
+
+ loadicons();
+
+ while ((line = nextline())) {
+ remove_line_continuation(line);
+ if (iscomment(line))
+ continue;
+ break;
+ }
+
+ while ((line = nextline())) {
+ remove_line_continuation(line);
+ if (iscomment(line))
+ continue;
+
+ count += 1;
+ rstrip(line);
+ indent = getindent(&line);
+ if (strchr(line, ' ') || strchr(line, '\t')) {
+ fprintf(stderr, "Line %zu in icons.mk contains unexpected whitespace\n", lineno);
+ exitstatus = 1;
+ }
+
+ if (!indent) {
+ fprintf(stderr, "Line %zu in icons.mk is not indented\n", lineno);
+ exitstatus = 1;
+ break;
+ }
+ indent -= 1;
+ if (indent > stacklen) {
+ fprintf(stderr, "Line %zu in icons.mk (%s) is overindented\n", lineno, line);
+ exitstatus = 1;
+ break;
+ }
+ if (stacksize <= indent) {
+ stacksize += 32;
+ stack = realloc(stack, stacksize * sizeof(*stack));
+ if (!stack) {
+ fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n");
+ exit(1);
+ }
+ }
+ stack[indent] = line;
+ stacklen = indent + 1;
+
+ file = getfile(line);
+ switch (getlink(file, &target)) {
+ case ERROR:
+ continue;
+ case NO_SUCH_FILE:
+ fprintf(stderr, "%s is listed but does not exist\n", line);
+ exitstatus = 1;
+ continue;
+ case REGULAR:
+ if (indent) {
+ fprintf(stderr, "%s is not a symlink but is listed as linking to %s\n", line, stack[indent - 1]);
+ exitstatus = 1;
+ continue;
+ }
+ break;
+ case SYMLINK:
+ if (!indent) {
+ fprintf(stderr, "%s is a symlink but not indented\n", line);
+ exitstatus = 1;
+ }
+ len = strlen(target);
+ if (len < 5 || strcmp(&target[len - 4], ".svg")) {
+ fprintf(stderr, "target of %s (%s) is not a .svg-file\n", line, target);
+ exitstatus = 1;
+ continue;
+ }
+ target[len -= 4] = '\0';
+ break;
+ case OTHER_TYPE:
+ default:
+ fprintf(stderr, "%s is listed as an icon but is not a regular file\n", line);
+ exitstatus = 1;
+ continue;
+ }
+
+ if (indent) {
+ if (stat(file, &st1)) {
+ if (errno == ENOENT) {
+ fprintf(stderr, "%s is a dangling symlink\n", line);
+ } else {
+ fprintf(stderr, "Failure at line %zu in icons.mk: stat %s: %s\n",
+ lineno, line, strerror(errno));
+ }
+ exitstatus = 1;
+ continue;
+ }
+
+ file = getfile(stack[indent - 1]);
+ if (stat(file, &st2)) {
+ fprintf(stderr, "Failure at line %zu in icons.mk (%s): stat %s: %s\n",
+ lineno, line, file, strerror(errno));
+ }
+
+ if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
+ diff = "same real file";
+ else
+ diff = "different real file";
+
+ fixed = 0;
+ while (target[0] == '.' && target[1] == '/') {
+ target = &target[2];
+ fixed = 1;
+ }
+ goal = rel(stack[indent - 1], line);
+ if (strcmp(target, goal)) {
+ fprintf(stderr, "%s links to %s.svg but should link to %s.svg [%s] (%s)\n",
+ line, target, goal, stack[indent - 1], diff);
+ exitstatus = 1;
+ continue;
+ }
+ if (fixed) {
+ fprintf(stderr, "Fixing symlink %s\n", line);
+ file = getfile(line);
+ *strchr(target, '\0') = '.'; /* restore ".svg" at the end of the string */
+ if (unlink(file)) {
+ fprintf(stderr, "... failed to unlink\n");
+ } else if (symlink(target, file)) {
+ fprintf(stderr, "... failed to create symlink (%s -> %s)\n", file, target);
+ exitstatus = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!count) {
+ fprintf(stderr, "No icons are listed in icons.mk\n");
+ exitstatus = 1;
+ }
+
+ free(stack);
+ free(text);
+ free(filebuf);
+ free(targetbuf);
+ free(relbuf);
+ return exitstatus;
+}
diff --git a/check/check-icons-listing b/check/check-icons-listing
deleted file mode 100755
index 404e089..0000000
--- a/check/check-icons-listing
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/bin/sh
-set -e
-
-rel () {
- to="$1"
- from="$2"
- while test $(printf '%s\n' "$to" "$from" | grep / | wc -l) = 2; do
- todir="$(printf '%s\n' "$to" | cut -d / -f 1)"
- fromdir="$(printf '%s\n' "$from" | cut -d / -f 1)"
- if test ! "$todir" = "$fromdir"; then
- break
- fi
- to="$(printf '%s\n' "$to" | cut -d / -f 2-)"
- from="$(printf '%s\n' "$frm" | cut -d / -f 2-)"
- done
- while test -n "$(printf '%s\n' "$from" | grep /)"; do
- from="$(printf '%s\n' "$from" | cut -d / -f 2-)"
- to="../$to"
- done
- printf '%s\n' "$to"
-}
-
-lines=$(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | wc -l)
-test ! $lines = 0
-
-test $(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | sed 's/\t/ /g' | grep '^ ' | wc -l) = $lines
-
-test $(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed '1s/^.*$//' | grep -n ' ' | wc -l) = 0
-
-stack=""
-i=0
-sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | sed 's/^\t/x/' | while read L; do
- i=$(( i + 1 ))
- printf 'Checking %i of %i\n\033[A' $i $lines >&2
- tabs=0
- while test ! $(printf '%s\n' "$L" | sed -n '/^x\t/p' | wc -l) = 0; do
- L="$(printf '%s\n' "$L" | sed 's/^x\t/x/')"
- tabs=$(( tabs + 1 ))
- done
- L="$(printf '%s\n' "$L" | sed 's/^x//')"
- f="scalable/$L.svg"
- if test ! $(printf '%s\n' $f | wc -l) = 1; then
- printf '\033[K%s contains whitespace\n' "$L" >&2
- exit 1
- fi
- tabsplus1=$(( tabs + 1 ))
- if test -z "$(printf '%s\n' $stack x | sed -n ${tabsplus1}p)"; then
- printf '\033[K%s is overtabulated\n' "$L" >&2
- exit 1
- else
- stack="$(printf '%s\n' $stack | head -n $tabs; printf '%s\n' "$L")"
- fi
- if test ! -e "$f"; then
- if test -L "$f"; then
- printf '\033[K%s is a dangling symlink\n' "$L" >&2
- else
- printf '\033[K%s is listed but does not exist\n' "$L" >&2
- fi
- exit 1
- fi
- if test $tabs = 0; then
- if test -L "$f"; then
- printf '\033[K%s is a symlink but not indented\n' "$L" >&2
- exit 1
- fi
- else
- goal_="$(printf '%s\n' $stack | sed -n "${tabs}p")"
- if test ! -L "$f"; then
- printf '\033[K%s is not a symlink but listed as linking to %s\n' "$L" "$goal" >&2
- exit 1
- fi
- target="$(realpath -- "$f")"
- goal="$(realpath -- "scalable/${goal_}.svg")"
- if test "$target" = "$goal"; then
- diff="same real file"
- else
- diff="different real file"
- fi
- target="$(readlink -- "$f")"
- goal="$(rel "${goal_}.svg" "$L.svg")"
- if test "$target" = "./$goal"; then
- ln -sf -- "$target" "$f"
- elif test ! "$target" = "$goal"; then
- printf '\033[K%s links to %s but should link to %s (%s)\n' "$L" "$target" "$goal" "$diff" >&2
- exit 1
- fi
- fi
-done
-printf '\033[K' >&2
diff --git a/check/find-duplicates b/check/find-duplicates
new file mode 100755
index 0000000..2517fb6
--- /dev/null
+++ b/check/find-duplicates
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+
+dups="$(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | tr -d '\t ' | sort | uniq -d)"
+
+if test -n "$dups"; then
+ printf 'The following files have been listed in icons.mk multiple times:\n%s\n' "$dups" >&2
+ exit 1
+fi