Rename bloom_test_and_insert() to bloom_insert().
[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 }