Add pretty-printing formating functions.
authorAndre Noll <maan@systemlinux.org>
Sat, 18 Oct 2008 15:20:58 +0000 (17:20 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 18 Oct 2008 15:20:58 +0000 (17:20 +0200)
This adds a generic format string parser. The new code is compiled
in but not yet used.

Makefile
format.c [new file with mode: 0644]
format.h [new file with mode: 0644]
string.c
string.h

index b595c68..2f1fb47 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-objects := adu.o string.o cmdline.o fd.o select.o create.o interactive.o select.cmdline.o
+objects := adu.o string.o cmdline.o fd.o select.o create.o interactive.o select.cmdline.o format.o
 all: adu
 version := 0.0.3
 
diff --git a/format.c b/format.c
new file mode 100644 (file)
index 0000000..3d1daa2
--- /dev/null
+++ b/format.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file format.c Functions for pretty-printing numbers and strings. */
+
+#include <dirent.h> /* readdir() */
+#include "adu.h"
+#include "gcc-compat.h"
+#include "fd.h"
+#include "string.h"
+#include "error.h"
+#include "format.h"
+enum alignment {ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER};
+
+struct num_format {
+       enum alignment align;
+       char unit;
+       int supress_unit;
+};
+
+struct string_format {
+       enum alignment align;
+};
+
+struct const_string {
+       char *string;
+};
+
+union atom_format {
+       struct string_format sf;
+       struct num_format nf;
+       struct const_string cs;
+};
+
+struct format_item {
+       struct atom *atom_ptr;
+       unsigned int width;
+       union atom_format af;
+};
+
+struct format_info {
+       struct atom *atoms;
+       struct format_item **items;
+};
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+
+static const char units[] = "bkmgth KMGTH";
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static char *find_next(char *cp)
+{
+       while (*cp) {
+               if (*cp == '%') {
+                       /* %( is the start of an atom;
+                        * %% is a quoted per-cent.
+                        */
+                       if (cp[1] == '(')
+                               return cp;
+                       else if (cp[1] == '%')
+                               cp++; /* skip over two % */
+                       /* otherwise this is a singleton, literal % */
+               }
+               cp++;
+       }
+       return NULL;
+}
+
+static struct format_item *make_const_string(char *cp, char *ep)
+{
+       struct format_item *fi = malloc(sizeof *fi);
+       char *op;
+
+       fi->atom_ptr = NULL;
+       fi->width = ep - cp;
+       op = fi->af.cs.string = malloc(fi->width + 1);
+       while (*cp && (!ep || cp < ep)) {
+               if (*cp == '%' && cp[1] == '%') {
+                       cp++;
+                       fi->width--;
+               }
+               *op = *cp;
+               cp++;
+               op++;
+       }
+       *op = '\0';
+       return fi;
+}
+
+static struct format_item *parse_atom(char *ap, struct atom *atoms)
+{
+       struct format_item *fi = NULL;
+       int i, n, len;
+       char *col, *ep = strchr(ap, ')');
+       char *err_msg = "malformed format string";
+
+       if (!ep)
+               goto err;
+       col = memchr(ap, ':', ep - ap);
+       if (col)
+               len = col - ap;
+       else
+               len = ep - ap;
+       for (i = 0; atoms[i].name; i++) {
+               int j;
+               struct atom *a = atoms + i;
+               if (strlen(a->name) != len)
+                       continue;
+               if (strncmp(a->name, ap, len))
+                       continue;
+               fi = malloc(sizeof(*fi));
+               fi->atom_ptr = a;
+               /* set defaults */
+               fi->width = 0;
+               switch (a->type) {
+               case AT_STRING:
+                       fi->af.sf.align = ALIGN_LEFT;
+                       break;
+               case AT_ID:
+                       fi->af.nf.align = ALIGN_RIGHT;
+                       fi->af.nf.unit = ' ';
+                       fi->af.nf.supress_unit = 0;
+                       break;
+               case AT_COUNT:
+                       fi->af.nf.align = ALIGN_RIGHT;
+                       fi->af.nf.unit = 'H';
+                       fi->af.nf.supress_unit = 0;
+                       break;
+               case AT_SIZE:
+                       fi->af.nf.align = ALIGN_RIGHT;
+                       fi->af.nf.unit = 'h';
+                       fi->af.nf.supress_unit = 0;
+                       break;
+               }
+               if (!col)
+                       goto success;
+               /* read alignment */
+               switch (col[1]) {
+               case 'l':
+                       fi->af.sf.align = ALIGN_LEFT;
+                       break;
+               case 'r':
+                       fi->af.nf.align = ALIGN_RIGHT;
+                       break;
+               case 'c':
+                       fi->af.nf.align = ALIGN_CENTER;
+                       break;
+               case ':': /* no alignment spec is OK */
+                       col--;
+                       break;
+               default:
+                       err_msg = "bad alignment spec";
+                       goto err;
+               }
+               switch (col[2]) {
+               case ')':
+                       goto success;
+               case ':':
+                       col += 2;
+                       break;
+               default:
+                       err_msg = "trailing garbage after alignment spec";
+                       goto err;
+               }
+               /* read width */
+               n = 0;
+               sscanf(col + 1, "%u%n", &fi->width, &n);
+               /* read unit */
+               switch (col[1 + n]) {
+               case ')':
+                       goto success;
+               case ':':
+                       col += 1 + n;
+                       break;
+               default:
+                       err_msg = "trailing garbage after width spec";
+                       goto err;
+               }
+               if (a->type != AT_SIZE && a->type != AT_COUNT) {
+                       err_msg = "no unit allowed here";
+                       goto err;
+               }
+               if (col[1] == '*') {
+                       fi->af.nf.supress_unit = 1;
+                       col++;
+               }
+               for (j = 0; units[j]; j++) {
+                       if (units[j] != col[1])
+                               continue;
+                       fi->af.nf.unit = units[j];
+                       break;
+               }
+               if (!units[j]) {
+                       err_msg = "bad unit spec";
+                       goto err;
+               }
+               if (col + 2 != ep) {
+                       err_msg = "trailing garbage after unit spec";
+                       goto err;
+               }
+               goto success;
+       }
+       err_msg = "invalid atom";
+err:
+       ERROR_LOG("%s\n", err_msg);
+       free(fi);
+       return NULL;
+success:
+       return fi;
+}
+
+struct format_info *parse_format_string(char *fmt, struct atom *atoms)
+{
+       char *cp, *ap, *ep;
+       int num_items;
+       struct format_info *info = malloc(sizeof(*info));
+
+       info->atoms = atoms;
+       info->items = NULL;
+       for (cp = fmt, num_items = 0; *cp && (ap = find_next(cp));
+                       cp = ep + 1, num_items++) {
+               if (cp < ap) {
+                       info->items = realloc(info->items,
+                               (num_items + 1) * sizeof(struct format_info *));
+                       info->items[num_items] = make_const_string(cp, ap);
+                       num_items++;
+               }
+               info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
+               info->items[num_items] = parse_atom(ap + 2, atoms);
+               if (!info->items[num_items]) {
+                       num_items--;
+                       goto err;
+               }
+               ep = strchr(ap, ')');
+       }
+       if (*cp) {
+               ep = cp + strlen(cp);
+               info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
+               info->items[num_items] = make_const_string(cp, ep);
+               num_items++;
+       }
+       info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
+       info->items[num_items] = NULL;
+       return info;
+err:
+       for (; num_items >= 0; num_items--) {
+               if (!info->items[num_items]->atom_ptr)
+                       free(info->items[num_items]->af.cs.string);
+               free(info->items[num_items]);
+       }
+       free(info->items);
+       free(info);
+       return NULL;
+}
+
+void free_format_info(struct format_info *info)
+{
+       int i;
+       struct format_item *item;
+
+       for (i = 0; (item = info->items[i]); i++) {
+               if (!item->atom_ptr)
+                       free(item->af.cs.string);
+               free(info->items[i]);
+       }
+       free(info->items);
+       free(info);
+}
+
+static char *align_string(const char *src, unsigned int width, enum alignment align)
+{
+       int len;
+
+       if (!width)
+               return adu_strdup(src);
+       if (align == ALIGN_LEFT)
+               return make_message("%-*s", width, src);
+       if (align == ALIGN_RIGHT)
+               return make_message("%*s", width, src);
+       len = strlen(src);
+       return make_message("%*s%*s", (width + len) / 2, src,
+               width - (width + len) / 2, "");
+}
+
+/** Compute the number of (decimal) digits of a number. */
+#define GET_NUM_DIGITS(x, num) { \
+       typeof((x)) _tmp = x; \
+       *num = 1; \
+       if ((x)) \
+               while ((_tmp) > 9) { \
+                       (_tmp) /= 10; \
+                       (*num)++; \
+               } \
+} \
+
+static long long unsigned normalize_number(long long unsigned num, char unit,
+       char *effective_unit)
+{
+       long long unsigned normalized_num;
+
+       if (unit == 'h') {
+               if (num < 1024ULL)
+                       *effective_unit = ' ';
+               else if (num < 1024ULL * 1024ULL)
+                       *effective_unit = 'k';
+               else if (num < 1024ULL * 1024ULL * 1024ULL)
+                       *effective_unit = 'm';
+               else if (num < 1024ULL * 1024ULL * 1024ULL * 1024ULL)
+                       *effective_unit = 'g';
+               else
+                       *effective_unit = 't';
+       } else if (unit == 'H') {
+               if (num < 1000ULL)
+                       *effective_unit = ' ';
+               else if (num < 1000ULL * 1000ULL)
+                       *effective_unit = 'K';
+               else if (num < 1000ULL * 1000ULL * 1000ULL)
+                       *effective_unit = 'M';
+               else if (num < 1000ULL * 1000ULL * 1000ULL * 1000ULL)
+                       *effective_unit = 'G';
+               else
+                       *effective_unit = 'T';
+       } else
+               *effective_unit = unit;
+       switch (*effective_unit) {
+       case ' ':
+       case 'b': normalized_num = num; break;
+       case 'k': normalized_num = num / 1024; break;
+       case 'm': normalized_num = num / 1024 / 1024; break;
+       case 'g': normalized_num = num / 1024 / 1024 / 1024; break;
+       case 't': normalized_num = num / 1024 / 1024 / 1024 / 1024; break;
+       case 'K': normalized_num = num / 1000; break;
+       case 'M': normalized_num = num / 1000 / 1000; break;
+       case 'G': normalized_num = num / 1000 / 1000 / 1000; break;
+       case 'T': normalized_num = num / 1000 / 1000 / 1000 / 1000; break;
+       default:
+               EMERG_LOG("BUG: invalid unit %c\n", *effective_unit);
+               exit(1);
+       }
+       return normalized_num;
+}
+
+static void get_unit_postfix(struct num_format *nf, char eu, enum atom_type type,
+               char postfix[2])
+{
+       if (nf->supress_unit) {
+               *postfix = '\0';
+               return;
+       }
+       postfix[0] = eu;
+       postfix[1] = '\0';
+       if (eu != ' ')
+               return;
+       if (nf->unit != 'h' && nf->unit != 'H')
+               return;
+       if (type != AT_SIZE)
+               return;
+       postfix[0] = 'b'; /* bytes */
+}
+
+static char *align_unsigned_int(long long unsigned num, unsigned int width,
+               struct num_format *nf, enum atom_type type)
+{
+       char eu; /* effective unit */
+       long long unsigned nnum = normalize_number(num, nf->unit, &eu);
+       char postfix[2] = "\0\0";
+       int len;
+
+       get_unit_postfix(nf, eu, type, postfix);
+       if (!width)
+               return make_message("%llu%s", nnum, postfix);
+       GET_NUM_DIGITS(nnum, &len);
+       len += strlen(postfix);
+       if (len > width)
+               width = len;
+       if (nf->align == ALIGN_LEFT)
+               return make_message("%llu%s%*s", nnum, postfix, width - len, "");
+       if (nf->align == ALIGN_RIGHT)
+               return make_message("%*s%llu%s", width - len, "", nnum, postfix);
+       return make_message("%*llu%s%*s", (width + len) / 2,
+               nnum, postfix, width - (width + len) / 2, "");
+}
+
+char *format_items(struct format_info *info, union atom_value *values)
+{
+       int i;
+       char *buf = NULL;
+
+       for (i = 0; info->items[i]; i++) {
+               struct atom *a;
+               struct format_item *fi = info->items[i];
+               union atom_format *af = &fi->af;
+               enum alignment align;
+               enum atom_type type;
+               char *val;
+               int idx;
+
+               if (!fi->atom_ptr) { /* const string */
+                       buf = adu_strcat(buf, af->cs.string);
+                       continue;
+               }
+               a = fi->atom_ptr;
+               type = a->type;
+               idx = a - info->atoms;
+
+               if (type == AT_STRING) {
+                       align = af->sf.align;
+                       val = align_string(values[idx].string_value, fi->width, align);
+               } else {
+                       char unit;
+                       align = af->nf.align;
+                       unit = af->nf.unit;
+                       val = align_unsigned_int(values[idx].num_value,
+                               fi->width, &af->nf, type);
+               }
+               buf = adu_strcat(buf, val);
+       }
+       return buf;
+}
diff --git a/format.h b/format.h
new file mode 100644 (file)
index 0000000..4929bff
--- /dev/null
+++ b/format.h
@@ -0,0 +1,20 @@
+/* format.h */
+enum atom_type {AT_STRING, AT_ID, AT_COUNT, AT_SIZE};
+/*
+ * STRING: %(foo:a:w)
+ * (U)INT: %(foo:a:w:u)
+ */
+struct atom {
+       const char const *name;
+       enum atom_type type;
+};
+
+union atom_value {
+       char *string_value;
+       long long unsigned num_value;
+};
+
+struct format_info; /* opaque */
+struct format_info *parse_format_string(char *fmt, struct atom *atoms);
+char *format_items(struct format_info *info, union atom_value *values);
+void free_format_info(struct format_info *info);
index 87f9c26..0b92968 100644 (file)
--- a/string.c
+++ b/string.c
@@ -129,6 +129,34 @@ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
        return msg;
 }
 
+/**
+ * adu's version of strcat().
+ *
+ * \param a String to be appended to.
+ * \param b String to append.
+ *
+ * Append \p b to \p a.
+ *
+ * \return If \a a is \p NULL, return a pointer to a copy of \a b, i.e.
+ * para_strcat(NULL, b) is equivalent to para_strdup(b). If \a b is \p NULL,
+ * return \a a without making a copy of \a a.  Otherwise, construct the
+ * concatenation \a c, free \a a (but not \a b) and return \a c.
+ *
+ * \sa strcat(3).
+ */
+__must_check __malloc char *adu_strcat(char *a, const char *b)
+{
+       char *tmp;
+
+       if (!a)
+               return adu_strdup(b);
+       if (!b)
+               return a;
+       tmp = make_message("%s%s", a, b);
+       free(a);
+       return tmp;
+}
+
 /** \cond LLONG_MAX and LLONG_LIN might not be defined. */
 #ifndef LLONG_MAX
 #define LLONG_MAX (1 << (sizeof(long) - 1))
index 7229b7d..0803565 100644 (file)
--- a/string.h
+++ b/string.h
@@ -10,6 +10,7 @@ __must_check __malloc void *adu_realloc(void *p, size_t size);
 __must_check __malloc void *adu_malloc(size_t size);
 __must_check __malloc void *adu_calloc(size_t size);
 __must_check __malloc char *adu_strdup(const char *s);
+__must_check __malloc char *adu_strcat(char *a, const char *b);
 __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
 __must_check int atoi64(const char *str, int64_t *result);
 int parse_uid_arg(const char *orig_arg, struct uid_range **ur);