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