select.c: Fix two return values.
[adu.git] / format.c
1 /*
2  * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file format.c Functions for pretty-printing numbers and strings. */
8
9 #include <dirent.h> /* readdir() */
10 #include "adu.h"
11 #include "gcc-compat.h"
12 #include "fd.h"
13 #include "string.h"
14 #include "error.h"
15 #include "format.h"
16
17 enum alignment {ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER};
18
19 struct num_format {
20         enum alignment align;
21         char unit;
22         int supress_unit;
23 };
24
25 struct string_format {
26         enum alignment align;
27 };
28
29 struct const_string {
30         char *string;
31 };
32
33 union atom_format {
34         struct string_format sf;
35         struct num_format nf;
36         struct const_string cs;
37 };
38
39 struct format_item {
40         struct atom *atom_ptr;
41         unsigned int width;
42         union atom_format af;
43 };
44
45 struct format_info {
46         struct atom *atoms;
47         struct format_item **items;
48 };
49
50 #include <stdio.h>
51 #include <string.h>
52 #include <stdlib.h>
53 #include <inttypes.h>
54
55
56 static const char units[] = "bkmgth KMGTH";
57 /*
58  * In a format string, find the next occurrence of %(atom).
59  */
60 static char *find_next(char *cp)
61 {
62         while (*cp) {
63                 if (*cp == '%') {
64                         /* %( is the start of an atom;
65                          * %% is a quoted per-cent.
66                          */
67                         if (cp[1] == '(')
68                                 return cp;
69                         else if (cp[1] == '%')
70                                 cp++; /* skip over two % */
71                         /* otherwise this is a singleton, literal % */
72                 }
73                 cp++;
74         }
75         return NULL;
76 }
77
78 static struct format_item *make_const_string(char *cp, char *ep)
79 {
80         struct format_item *fi = malloc(sizeof *fi);
81         char *op;
82
83         fi->atom_ptr = NULL;
84         fi->width = ep - cp;
85         op = fi->af.cs.string = malloc(fi->width + 1);
86         while (*cp && (!ep || cp < ep)) {
87                 if (*cp == '%' && cp[1] == '%') {
88                         cp++;
89                         fi->width--;
90                 }
91                 *op = *cp;
92                 cp++;
93                 op++;
94         }
95         *op = '\0';
96         return fi;
97 }
98
99 static int parse_atom(char *ap, struct atom *atoms, struct format_item **result)
100 {
101         struct format_item *fi = NULL;
102         int i, n, len;
103         char *col, *ep = strchr(ap, ')');
104         int ret = -E_MALFORMED_FORMAT;
105
106         if (!ep)
107                 goto err;
108         col = memchr(ap, ':', ep - ap);
109         if (col)
110                 len = col - ap;
111         else
112                 len = ep - ap;
113         for (i = 0; atoms[i].name; i++) {
114                 int j;
115                 struct atom *a = atoms + i;
116                 if (strlen(a->name) != len)
117                         continue;
118                 if (strncmp(a->name, ap, len))
119                         continue;
120                 fi = malloc(sizeof(*fi));
121                 fi->atom_ptr = a;
122                 /* set defaults */
123                 fi->width = 0;
124                 switch (a->type) {
125                 case AT_STRING:
126                         fi->af.sf.align = ALIGN_LEFT;
127                         break;
128                 case AT_ID:
129                         fi->af.nf.align = ALIGN_RIGHT;
130                         fi->af.nf.unit = ' ';
131                         fi->af.nf.supress_unit = 0;
132                         break;
133                 case AT_COUNT:
134                         fi->af.nf.align = ALIGN_RIGHT;
135                         fi->af.nf.unit = 'H';
136                         fi->af.nf.supress_unit = 0;
137                         break;
138                 case AT_SIZE:
139                         fi->af.nf.align = ALIGN_RIGHT;
140                         fi->af.nf.unit = 'h';
141                         fi->af.nf.supress_unit = 0;
142                         break;
143                 }
144                 if (!col)
145                         goto success;
146                 /* read alignment */
147                 switch (col[1]) {
148                 case 'l':
149                         fi->af.sf.align = ALIGN_LEFT;
150                         break;
151                 case 'r':
152                         fi->af.nf.align = ALIGN_RIGHT;
153                         break;
154                 case 'c':
155                         fi->af.nf.align = ALIGN_CENTER;
156                         break;
157                 case ':': /* no alignment spec is OK */
158                         col--;
159                         break;
160                 default:
161                         ret = -E_BAD_ALIGN_SPEC;
162                         goto err;
163                 }
164                 switch (col[2]) {
165                 case ')':
166                         goto success;
167                 case ':':
168                         col += 2;
169                         break;
170                 default:
171                         ret = -E_TRAILING_GARBAGE;
172                         goto err;
173                 }
174                 /* read width */
175                 n = 0;
176                 sscanf(col + 1, "%u%n", &fi->width, &n);
177                 /* read unit */
178                 switch (col[1 + n]) {
179                 case ')':
180                         goto success;
181                 case ':':
182                         col += 1 + n;
183                         break;
184                 default:
185                         ret = -E_TRAILING_GARBAGE;
186                         goto err;
187                 }
188                 if (a->type != AT_SIZE && a->type != AT_COUNT) {
189                         ret = -E_UNIT;
190                         goto err;
191                 }
192                 if (col[1] == '*') {
193                         fi->af.nf.supress_unit = 1;
194                         col++;
195                 }
196                 for (j = 0; units[j]; j++) {
197                         if (units[j] != col[1])
198                                 continue;
199                         fi->af.nf.unit = units[j];
200                         break;
201                 }
202                 if (!units[j]) {
203                         ret = -E_BAD_UNIT;
204                         goto err;
205                 }
206                 if (col + 2 != ep) {
207                         ret = -E_TRAILING_GARBAGE;
208                         goto err;
209                 }
210                 goto success;
211         }
212         ret = -E_BAD_ATOM;
213 err:
214         free(fi);
215         return ret;
216 success:
217         *result = fi;
218         return 1;
219 }
220
221 /**
222  * Parse the given string according to the list of given atoms.
223  *
224  * \param fmt The format string.
225  * \param atoms The array of valid atoms.
226  * \param result Points to a format_info structure for later use.
227  *
228  * \return Standard.
229  */
230 int parse_format_string(char *fmt, struct atom *atoms, struct format_info **result)
231 {
232         char *cp, *ap, *ep;
233         int num_items, ret;
234         struct format_info *info = malloc(sizeof(*info));
235
236         info->atoms = atoms;
237         info->items = NULL;
238         for (cp = fmt, num_items = 0; *cp && (ap = find_next(cp));
239                         cp = ep + 1, num_items++) {
240                 if (cp < ap) {
241                         info->items = realloc(info->items,
242                                 (num_items + 1) * sizeof(struct format_info *));
243                         info->items[num_items] = make_const_string(cp, ap);
244                         num_items++;
245                 }
246                 info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
247                 ret = parse_atom(ap + 2, atoms, info->items + num_items);
248                 if (ret < 0) {
249                         num_items--;
250                         goto err;
251                 }
252                 ep = strchr(ap, ')');
253         }
254         if (*cp) {
255                 ep = cp + strlen(cp);
256                 info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
257                 info->items[num_items] = make_const_string(cp, ep);
258                 num_items++;
259         }
260         info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
261         info->items[num_items] = NULL;
262         *result = info;
263         return 1;
264 err:
265         for (; num_items >= 0; num_items--) {
266                 if (!info->items[num_items]->atom_ptr)
267                         free(info->items[num_items]->af.cs.string);
268                 free(info->items[num_items]);
269         }
270         free(info->items);
271         free(info);
272         *result = NULL;
273         return ret;
274 }
275
276 /**
277  * Free a struct of type \a format_info.
278  *
279  * \param info Pointer to the format info to be freed.
280  *
281  * It's OK to pass a \p NULL pointer to this function in which case the
282  * function does nothing.
283  */
284 void free_format_info(struct format_info *info)
285 {
286         int i;
287         struct format_item *item;
288
289         if (!info)
290                 return;
291
292         for (i = 0; (item = info->items[i]); i++) {
293                 if (!item->atom_ptr)
294                         free(item->af.cs.string);
295                 free(info->items[i]);
296         }
297         free(info->items);
298         free(info);
299 }
300
301 static char *align_string(const char *src, unsigned int width, enum alignment align)
302 {
303         int len;
304
305         if (!width)
306                 return adu_strdup(src);
307         if (align == ALIGN_LEFT)
308                 return make_message("%-*s", width, src);
309         if (align == ALIGN_RIGHT)
310                 return make_message("%*s", width, src);
311         len = strlen(src);
312         return make_message("%*s%*s", (width + len) / 2, src,
313                 width - (width + len) / 2, "");
314 }
315
316 /** Compute the number of (decimal) digits of a number. */
317 #define GET_NUM_DIGITS(x, num) { \
318         typeof((x)) _tmp = x; \
319         *num = 1; \
320         if ((x)) \
321                 while ((_tmp) > 9) { \
322                         (_tmp) /= 10; \
323                         (*num)++; \
324                 } \
325 } \
326
327 static long long unsigned normalize_number(long long unsigned num, char unit,
328         char *effective_unit)
329 {
330         long long unsigned normalized_num;
331
332         if (unit == 'h') {
333                 if (num < 1024ULL)
334                         *effective_unit = ' ';
335                 else if (num < 1024ULL * 1024ULL)
336                         *effective_unit = 'k';
337                 else if (num < 1024ULL * 1024ULL * 1024ULL)
338                         *effective_unit = 'm';
339                 else if (num < 1024ULL * 1024ULL * 1024ULL * 1024ULL)
340                         *effective_unit = 'g';
341                 else
342                         *effective_unit = 't';
343         } else if (unit == 'H') {
344                 if (num < 1000ULL)
345                         *effective_unit = ' ';
346                 else if (num < 1000ULL * 1000ULL)
347                         *effective_unit = 'K';
348                 else if (num < 1000ULL * 1000ULL * 1000ULL)
349                         *effective_unit = 'M';
350                 else if (num < 1000ULL * 1000ULL * 1000ULL * 1000ULL)
351                         *effective_unit = 'G';
352                 else
353                         *effective_unit = 'T';
354         } else
355                 *effective_unit = unit;
356         switch (*effective_unit) {
357         case ' ':
358         case 'b': normalized_num = num; break;
359         case 'k': normalized_num = num / 1024; break;
360         case 'm': normalized_num = num / 1024 / 1024; break;
361         case 'g': normalized_num = num / 1024 / 1024 / 1024; break;
362         case 't': normalized_num = num / 1024 / 1024 / 1024 / 1024; break;
363         case 'K': normalized_num = num / 1000; break;
364         case 'M': normalized_num = num / 1000 / 1000; break;
365         case 'G': normalized_num = num / 1000 / 1000 / 1000; break;
366         case 'T': normalized_num = num / 1000 / 1000 / 1000 / 1000; break;
367         default:
368                 EMERG_LOG("BUG: invalid unit %c\n", *effective_unit);
369                 exit(1);
370         }
371         return normalized_num;
372 }
373
374 static void get_unit_postfix(struct num_format *nf, char eu, enum atom_type type,
375                 char postfix[2])
376 {
377         if (nf->supress_unit) {
378                 *postfix = '\0';
379                 return;
380         }
381         postfix[0] = eu;
382         postfix[1] = '\0';
383         if (eu != ' ')
384                 return;
385         if (nf->unit != 'h' && nf->unit != 'H')
386                 return;
387         if (type != AT_SIZE)
388                 return;
389         postfix[0] = 'b'; /* bytes */
390 }
391
392 static char *align_unsigned_int(long long unsigned num, unsigned int width,
393                 struct num_format *nf, enum atom_type type)
394 {
395         char eu; /* effective unit */
396         long long unsigned nnum = normalize_number(num, nf->unit, &eu);
397         char postfix[2] = "\0\0";
398         int len;
399
400         get_unit_postfix(nf, eu, type, postfix);
401         if (!width)
402                 return make_message("%llu%s", nnum, postfix);
403         GET_NUM_DIGITS(nnum, &len);
404         len += strlen(postfix);
405         if (len > width)
406                 width = len;
407         if (nf->align == ALIGN_LEFT)
408                 return make_message("%llu%s%*s", nnum, postfix, width - len, "");
409         if (nf->align == ALIGN_RIGHT)
410                 return make_message("%*s%llu%s", width - len, "", nnum, postfix);
411         return make_message("%*llu%s%*s", (width + len) / 2,
412                 nnum, postfix, width - (width + len) / 2, "");
413 }
414
415 /**
416  * Pretty-format the given values according to \a info.
417  *
418  * \param info The formating information.
419  * \param values The contents of the atoms.
420  *
421  * \return A string that must be freed by the caller.
422  */
423 char *format_items(struct format_info *info, union atom_value *values)
424 {
425         int i;
426         char *buf = NULL;
427
428         if (!info)
429                 return NULL;
430         for (i = 0; info->items[i]; i++) {
431                 struct atom *a;
432                 struct format_item *fi = info->items[i];
433                 union atom_format *af = &fi->af;
434                 enum alignment align;
435                 enum atom_type type;
436                 char *val;
437                 int idx;
438
439                 if (!fi->atom_ptr) { /* const string */
440                         buf = adu_strcat(buf, af->cs.string);
441                         continue;
442                 }
443                 a = fi->atom_ptr;
444                 type = a->type;
445                 idx = a - info->atoms;
446
447                 if (type == AT_STRING) {
448                         align = af->sf.align;
449                         val = align_string(values[idx].string_value, fi->width, align);
450                 } else {
451                         char unit;
452                         align = af->nf.align;
453                         unit = af->nf.unit;
454                         val = align_unsigned_int(values[idx].num_value,
455                                 fi->width, &af->nf, type);
456                 }
457                 buf = adu_strcat(buf, val);
458                 free(val);
459         }
460         return buf;
461 }