--- /dev/null
+/*
+ * 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;
+}