aboutsummaryrefslogtreecommitdiff
path: root/center.c
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2022-02-08 00:37:20 +0100
committerThomas Voss <mail@thomasvoss.com> 2022-02-08 00:41:43 +0100
commita3183ff88a1aaac66bd49f258fdd02fdeff421ca (patch)
treeb59a284a697863cd7c26b29fe2768e1852c15ed8 /center.c
Initial commit
This initial commit includes the following: - A gitignore file - A license (0-Clause BSD) - A Makefile supporting installation - A manual page written in mdoc(7) - A fully working initial implementation
Diffstat (limited to 'center.c')
-rw-r--r--center.c163
1 files changed, 163 insertions, 0 deletions
diff --git a/center.c b/center.c
new file mode 100644
index 0000000..ce0d50e
--- /dev/null
+++ b/center.c
@@ -0,0 +1,163 @@
+#define POSIXLY_CORRECT
+#define _POSIX_C_SOURCE 200809L
+
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ESC 033
+
+static void center(FILE *);
+static int cols(void);
+static int utf8len(const char *);
+static int noesclen(const char *);
+static int matchesc(const char *);
+
+extern int optind;
+extern char *optarg;
+
+int rval;
+long width;
+int (*lenfunc)(const char *) = noesclen;
+
+int
+main(int argc, char **argv)
+{
+ int opt;
+ char *endptr;
+
+ while ((opt = getopt(argc, argv, ":elsw:")) != -1) {
+ switch (opt) {
+ case 'e':
+ lenfunc = utf8len;
+ break;
+ case 'w':
+ width = strtol(optarg, &endptr, 0);
+ if (*optarg == '\0' || *endptr != '\0')
+ errx(EXIT_FAILURE, "Invalid integer '%s'", optarg);
+ if (width <= 0)
+ errx(EXIT_FAILURE, "Width must be >0");
+ if (errno == ERANGE || width > INT_MAX)
+ warnx("Potential overflow of given width");
+ break;
+ default:
+ fprintf(stderr, "Usage: %s [-e] [-w width]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!width && (width = cols()) == -1)
+ errx(EXIT_FAILURE, "Unable to determine output width");
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ center(stdin);
+ else do {
+ if (strcmp(*argv, "-") == 0)
+ center(stdin);
+ else {
+ FILE *fp;
+ if ((fp = fopen(*argv, "r")) == NULL)
+ errx(EXIT_FAILURE, "fopen");
+ center(fp);
+ fclose(fp);
+ }
+ } while (*++argv);
+
+ return rval;
+}
+
+/* Write the contents of the file pointed to by `fp' center aligned to the standard output */
+void
+center(FILE *fp)
+{
+ char *line = NULL;
+ size_t bs = 0;
+
+ while (getline(&line, &bs, fp) != -1) {
+ int len, tabs = 0;
+ char *match = line;
+
+ while (strchr(match, '\t')) {
+ match++;
+ tabs++;
+ }
+
+ len = lenfunc(line) + tabs * 8 - tabs + 1;
+ printf("%*s", ((int) width - len) / 2 + len, line);
+ }
+ if (ferror(fp)) {
+ warn("getline");
+ rval = EXIT_FAILURE;
+ }
+}
+
+/* Return the column width of the output device if it is a TTY. If it is not a TTY then return -1 */
+int
+cols(void)
+{
+ struct winsize w;
+
+ if (!isatty(STDOUT_FILENO))
+ return -1;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1)
+ err(EXIT_FAILURE, "ioctl");
+
+ return w.ws_col;
+}
+
+/* Return the length of the UTF-8 encoded string `s' in characters, not bytes */
+int
+utf8len(const char *s)
+{
+ int l = 0;
+
+ while (*s)
+ l += (*s++ & 0xC0) != 0x80;
+
+ return l;
+}
+
+/* Return the length of the UTF-8 encoded string `s' in characters, not bytes. Additionally this
+ * function ignores ANSI color escape sequences when computing the length.
+ */
+int
+noesclen(const char *s)
+{
+ int off = 0;
+ const char *d = s;
+
+ while ((d = strchr(d, ESC)) != NULL)
+ off += matchesc(++d);
+
+ return utf8len(s) + off;
+}
+
+/* Return the length of the ANSI color escape sequence at the beginning of the string `s'. If no
+ * escape sequence is matched then 0 is returned. The local variable `c' is initialized to 3 in order
+ * to account for the leading ESC, the following `[', and the trailing `m'.
+ */
+int
+matchesc(const char *s)
+{
+ int c = 3;
+
+ if (*s++ != '[')
+ return 0;
+ while (isdigit(*s) || *s == ';') {
+ c++;
+ s++;
+ }
+
+ return *s == 'm' ? c : 0;
+}