X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=format.c;fp=format.c;h=3d1daa2aca20fde5f7b993bb01d6b178818f8d3b;hb=0a8f1be6ca96c7050f02129bb275c926671007f4;hp=0000000000000000000000000000000000000000;hpb=1f948aa655d9a7389477baa1489f86e548ec064e;p=adu.git diff --git a/format.c b/format.c new file mode 100644 index 0000000..3d1daa2 --- /dev/null +++ b/format.c @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file format.c Functions for pretty-printing numbers and strings. */ + +#include /* 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 +#include +#include +#include + + +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; +}