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