From: Andre Noll Date: Sat, 18 Oct 2008 18:50:06 +0000 (+0200) Subject: Merge commit 'fml/master' X-Git-Tag: v0.0.4~37 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=4c71ff7f9b6afe7c15331a1cc9e134c2db65cd73;hp=e6b9f49fcde364aeff6c0d38e66694eeac0573b4;p=adu.git Merge commit 'fml/master' --- diff --git a/Makefile b/Makefile index b595c68..2f1fb47 100644 --- 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 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; +} diff --git a/format.h b/format.h new file mode 100644 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); diff --git a/string.c b/string.c index 87f9c26..0b92968 100644 --- 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)) diff --git a/string.h b/string.h index 7229b7d..0803565 100644 --- 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);