tabular.c (12708B)
1#include "tabular.h" 2#include "hmap.h" 3#include "dvec.h" 4#include "allocator.h" 5 6#include <sys/ioctl.h> 7#include <unistd.h> 8#include <err.h> 9#include <string.h> 10#include <stdbool.h> 11#include <stdarg.h> 12#include <stdio.h> 13#include <stdint.h> 14 15#define ARRLEN(x) (sizeof(x)/sizeof(*(x))) 16 17struct col { 18 struct tabular_col tabular; 19 const char *hide_out; 20}; 21 22static bool print_style(FILE *file, const struct tabular_cfg *cfg, 23 const struct tabular_row *row, const struct tabular_col *col); 24 25static struct tabular_row *row_gen(const struct tabular_user *user); 26static char *col_str(const struct tabular_user *user_row, 27 const struct tabular_user *user_col); 28static bool col_hidden(const struct tabular_user *user_row, 29 const struct tabular_user *user_col); 30 31static const struct allocator *ga = &stdlib_strict_heap_allocator; 32 33static bool hide_empty = true; 34static bool skip_empty_lines = true; 35static bool skip_empty_entries = false; 36static bool header_underline = false; 37 38static char entry_sep = '\t'; 39static char line_sep = '\n'; 40 41static struct hmap colmap; 42static struct dvec cols; 43static size_t colcnt = 0; 44 45static struct dvec input; 46static size_t input_off = 0; 47static bool input_done = false; 48 49static struct dvec line; 50static struct dvec entries; 51static size_t linecnt = 0; 52 53static struct tabular_cfg cfg = { 54 .colors = 256, 55 56 .columns = NULL, 57 .column_count = 0, 58 59 .fit_rows = false, 60 61 .hsep = "│", 62 .vsep = "─", 63 .xsep = "┼", 64 65 .outw = 0, 66 .outh = 0, 67 68 .lpad = 0, 69 .rpad = 0, 70 71 .user.ptr = NULL, 72 .row_gen = row_gen, 73 .print_style = print_style, 74 75 .skip_lines = 4, 76 77 .allocator = &stdlib_heap_allocator 78}; 79 80static void __attribute__((noreturn)) 81die(const char *fmt, ...) 82{ 83 va_list ap; 84 85 fputs("tabular: ", stderr); 86 va_start(ap, fmt); 87 vfprintf(stderr, fmt, ap); 88 va_end(ap); 89 if (*fmt && fmt[strlen(fmt)-1] == ':') { 90 fputc(' ', stderr); 91 perror(NULL); 92 } else { 93 fputc('\n', stderr); 94 } 95 96 exit(1); 97} 98 99static bool 100print_style(FILE *file, const struct tabular_cfg *cfg, 101 const struct tabular_row *row, const struct tabular_col *col) 102{ 103 if (cfg->colors == 256) { 104 if (!col) { /* separators */ 105 fprintf(file, "\x1b[90m"); 106 return true; 107 } else if (!row) { /* header */ 108 fprintf(file, "\x1b[1m"); 109 if (header_underline) 110 fprintf(file, "\x1b[4m"); 111 return true; 112 } 113 } 114 115 return false; 116} 117 118static bool 119read_line(void) 120{ 121 uint8_t *tok, *sep, *end, *line_end; 122 ssize_t n; 123 124 if (input_done) return false; 125 126 while (1) { 127 while (!(line_end = memchr(input.data + input_off, 128 line_sep, input.len - input_off))) { 129 dvec_rm(&input, 0, input_off); 130 input_off = 0; 131 dvec_reserve(&input, input.len + BUFSIZ + 1); 132 n = read(0, input.data + input.len, BUFSIZ); 133 if (n <= 0) { 134 input_done = true; 135 line_end = input.data + input.len; 136 break; 137 } 138 input.len += (size_t) n; 139 } 140 if (input_done && !input.len) return false; 141 142 if (line_end != input.data || !skip_empty_lines) 143 break; 144 input_off += 1; 145 } 146 *(char *)line_end = '\0'; 147 148 dvec_clear(&entries); 149 tok = input.data + input_off; 150 while (tok) { 151 sep = memchr(tok, entry_sep, 152 (size_t) (input.data + input.len - tok)); 153 end = (sep && sep < line_end) ? sep : line_end; 154 *(char *)end = '\0'; 155 if (tok != end || !skip_empty_entries) { 156 dvec_add_back(&entries, 1); 157 *(size_t *)dvec_back(&entries) = 158 (size_t) (tok - input.data) - input_off; 159 } 160 tok = (sep && sep < line_end) ? sep + 1 : NULL; 161 } 162 163 dvec_clear(&line); 164 dvec_add_back(&line, (size_t) (line_end - input.data) - input_off + 1); 165 memcpy(line.data, input.data + input_off, line.len); 166 input_off += line.len; 167 168 return true; 169} 170 171static struct tabular_row * 172row_gen(const struct tabular_user *user_cfg) 173{ 174 struct tabular_row *row; 175 size_t i; 176 int rc; 177 178 if (!read_line()) return NULL; 179 180 row = tabular_alloc_row(&cfg, &rc, 181 (struct tabular_user) { .id = linecnt++ }); 182 if (!row) errx(1, "tabular_append_row %i", rc); 183 184 for (i = 0; i < cols.len; i++) { 185 tabular_load_row_entry_hidden(&cfg, row, i); 186 tabular_load_row_entry_str(&cfg, row, i); 187 } 188 189 return row; 190} 191 192static char * 193col_str(const struct tabular_user *user_row, const struct tabular_user *user_col) 194{ 195 struct col *col = user_col->ptr; 196 char *str; 197 size_t *off; 198 199 if (col->tabular.user.id >= dvec_len(&entries)) 200 return NULL; 201 202 off = dvec_at(&entries, col->tabular.user.id); 203 str = strdup(line.data + *off); 204 if (!str) die("strdup:"); 205 206 return str; 207} 208 209static bool 210col_hidden(const struct tabular_user *user, const struct tabular_user *user_col) 211{ 212 struct col *col = user_col->ptr; 213 size_t *off; 214 215 if (col->tabular.user.id >= dvec_len(&entries)) 216 return hide_empty; 217 218 off = dvec_at(&entries, col->tabular.user.id); 219 220 if (col->hide_out && !strcmp(line.data + *off, col->hide_out)) 221 return true; 222 223 return hide_empty && !strcmp(line.data + *off, ""); 224} 225 226static bool 227bool_arg(const char *arg, const char *name) 228{ 229 if (!strcmp(arg, "1") || !strcmp(arg, "true")) 230 return true; 231 else if (!strcmp(arg, "0") || !strcmp(arg, "false")) 232 return false; 233 else 234 die("bad %s", name); 235} 236 237static void 238parse(int argc, const char **argv) 239{ 240 struct col *col; 241 struct tabular_col *tcol; 242 const char **arg, **dst; 243 struct hmap_link *link; 244 struct hmap_iter iter; 245 struct winsize ws; 246 char namebuf[64]; 247 char *end, *c, *upper; 248 size_t len; 249 int rc, n; 250 251 hmap_init(&colmap, 16, hmap_str_hash, hmap_str_keycmp, ga); 252 dvec_init(&cols, sizeof(struct tabular_col), 0, ga); 253 254 /* get general flags */ 255 for (dst = arg = argv + 1; *arg; arg++) { 256 if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { 257 fprintf(stderr, "Usage: tabular " 258 "[--col NAME].. [--NAME-OPT VAL]..\n"); 259 exit(1); 260 } else if (!strcmp(*arg, "--hsep")) { 261 if (!*++arg) die("missing args"); 262 cfg.hsep = *arg; 263 } else if (!strcmp(*arg, "--vsep")) { 264 if (!*++arg) die("missing args"); 265 cfg.vsep = *arg; 266 } else if (!strcmp(*arg, "--xsep")) { 267 if (!*++arg) die("missing args"); 268 cfg.xsep = *arg; 269 } else if (!strcmp(*arg, "--lpad")) { 270 if (!*++arg) die("missing args"); 271 cfg.lpad = strtoul(*arg, &end, 10); 272 if (end && *end) die("bad %s", arg[-1]); 273 } else if (!strcmp(*arg, "--rpad")) { 274 if (!*++arg) die("missing args"); 275 cfg.rpad = strtoul(*arg, &end, 10); 276 if (end && *end) die("bad %s", arg[-1]); 277 } else if (!strcmp(*arg, "--skip-lines")) { 278 if (!*++arg) die("missing args"); 279 cfg.skip_lines = strtoul(*arg, &end, 10); 280 if (end && *end) die("bad %s", arg[-1]); 281 } else if (!strcmp(*arg, "--fit-rows")) { 282 if (!*++arg) die("missing args"); 283 cfg.fit_rows = bool_arg(*arg, "--fit-rows"); 284 } else if (!strcmp(*arg, "--outw")) { 285 if (!*++arg) die("missing args"); 286 cfg.outw = strtoul(*arg, &end, 10); 287 if (end && *end) die("bad %s", arg[-1]); 288 } else if (!strcmp(*arg, "--outh")) { 289 if (!*++arg) die("missing args"); 290 cfg.outh = strtoul(*arg, &end, 10); 291 if (end && *end) die("bad %s", arg[-1]); 292 } else if (!strcmp(*arg, "--skip-lines")) { 293 if (!*++arg) die("missing args"); 294 cfg.skip_lines = strtoul(*arg, &end, 10); 295 if (end && *end) die("bad %s", arg[-1]); 296 } else if (!strcmp(*arg, "--hide-empty")) { 297 if (!*++arg) die("missing args"); 298 hide_empty = bool_arg(*arg, arg[-1]); 299 } else if (!strcmp(*arg, "--line-sep")) { 300 if (!*++arg || !**arg || *(*arg+1)) 301 die("missing args"); 302 line_sep = **arg; 303 } else if (!strcmp(*arg, "--entry-sep")) { 304 if (!*++arg || !**arg || *(*arg+1)) 305 die("missing args"); 306 entry_sep = **arg; 307 } else if (!strcmp(*arg, "--header-underline")) { 308 if (!*++arg) die("missing args"); 309 header_underline = bool_arg(*arg, arg[-1]); 310 } else if (!strcmp(*arg, "--skip-empty-entries")) { 311 if (!*++arg) die("missing args"); 312 skip_empty_entries = bool_arg(*arg, arg[-1]); 313 } else if (!strcmp(*arg, "--skip-empty-lines")) { 314 if (!*++arg) die("missing args"); 315 skip_empty_lines = bool_arg(*arg, arg[-1]); 316 if (!*++arg) die("missing args"); 317 } else if (!strcmp(*arg, "--colors")) { 318 if (!*++arg) die("missing args"); 319 cfg.colors = (int) strtol(*arg, &end, 10); 320 if (end && *end) die("bad %s", arg[-1]); 321 } else { 322 *dst++ = *arg; 323 } 324 } 325 *dst = NULL; 326 327 if (!cfg.outw || !cfg.outh) { 328 rc = ioctl(1, TIOCGWINSZ, &ws); 329 if (!rc) { 330 cfg.outw = ws.ws_col; 331 cfg.outh = ws.ws_row; 332 } else { 333 cfg.outw = 80; 334 cfg.outh = 26; 335 } 336 } 337 338 /* get columns */ 339 for (dst = arg = argv + 1; *arg; arg++) { 340 if (!strcmp(*arg, "--col")) { 341 if (!*++arg) die("missing args"); 342 col = ga->alloc(ga, sizeof(struct col), NULL); 343 tcol = &col->tabular; 344 345 if (strlen(*arg) > 63) die("col name too long"); 346 strncpy(namebuf, *arg, 64); 347 for (c = namebuf; *c; c++) 348 *c = (*c >= 'a' && *c <= 'z') ? *c - 32 : *c; 349 upper = strdup(namebuf); 350 if (!upper) die("strdup:"); 351 352 col->hide_out = NULL; 353 tcol->name = *arg; 354 tcol->align = TABULAR_ALIGN_LEFT; 355 tcol->essential = true; 356 tcol->is_hidden = col_hidden; 357 tcol->to_str = col_str; 358 tcol->lpad = 0; 359 tcol->rpad = 0; 360 tcol->minwidth = strlen(tcol->name); 361 tcol->maxwidth = cfg.outw; 362 tcol->strategy = TABULAR_WRAP_WORDAWARE; 363 tcol->squashable = false; 364 tcol->user.id = colcnt++; 365 rc = hmap_add(&colmap, 366 (struct hmap_key) { .p = upper }, 367 (struct hmap_val) { .p = col }); 368 if (rc) die("duplicate col %s", tcol->name); 369 } else { 370 *dst++ = *arg; 371 } 372 } 373 *dst = NULL; 374 375 /* set column attrs */ 376 for (dst = arg = argv + 1; *arg; arg++) { 377 if (!strncmp(*arg, "--", 2)) { 378 n = 0; 379 rc = sscanf(*arg, "--%63[^-]-%n", namebuf, &n); 380 if (rc != 1 || *(*arg+n-1) != '-') goto skip; 381 link = hmap_get(&colmap, 382 (struct hmap_key) { .p = namebuf }); 383 if (!link) goto skip; 384 col = link->value._p; 385 tcol = &col->tabular; 386 387 if (!strcmp(*arg + n, "lpad")) { 388 if (!*++arg) die("missing args"); 389 tcol->lpad = strtoul(*arg, &end, 10); 390 if (end && *end) die("bad %s", arg[-1]); 391 len = tcol->lpad + strlen(tcol->name) + tcol->rpad; 392 if (tcol->minwidth < len) 393 tcol->minwidth = len; 394 } else if (!strcmp(*arg + n, "rpad")) { 395 if (!*++arg) die("missing args"); 396 tcol->rpad = strtoul(*arg, &end, 10); 397 if (end && *end) die("bad %s", arg[-1]); 398 len = tcol->lpad + strlen(tcol->name) + tcol->rpad; 399 if (tcol->minwidth < len) 400 tcol->minwidth = len; 401 } else if (!strcmp(*arg + n, "align")) { 402 if (!*++arg) die("missing args"); 403 if (!strcmp(*arg, "left")) { 404 tcol->align = TABULAR_ALIGN_LEFT; 405 } else if (!strcmp(*arg, "right")) { 406 tcol->align = TABULAR_ALIGN_RIGHT; 407 } else if (!strcmp(*arg, "center")) { 408 tcol->align = TABULAR_ALIGN_CENTER; 409 } else { 410 die("bad %s", arg[-1]); 411 } 412 } else if (!strcmp(*arg + n, "squashable")) { 413 if (!*++arg) die("missing args"); 414 tcol->squashable = bool_arg(*arg, arg[-1]); 415 } else if (!strcmp(*arg + n, "essential")) { 416 if (!*++arg) die("missing args"); 417 tcol->essential = bool_arg(*arg, arg[-1]); 418 } else if (!strcmp(*arg + n, "minwidth")) { 419 if (!*++arg) die("missing args"); 420 tcol->minwidth = strtoul(*arg, &end, 10); 421 if (tcol->minwidth > tcol->maxwidth) 422 tcol->maxwidth = tcol->minwidth; 423 if (end && *end) die("bad %s", arg[-1]); 424 } else if (!strcmp(*arg + n, "maxwidth")) { 425 if (!*++arg) die("missing args"); 426 tcol->maxwidth = strtoul(*arg, &end, 10); 427 if (end && *end) die("bad %s", arg[-1]); 428 } else if (!strcmp(*arg + n, "strategy")) { 429 if (!*++arg) die("missing args"); 430 if (!strcmp(*arg, "word-aware")) { 431 tcol->strategy = TABULAR_WRAP_WORDAWARE; 432 } else if (!strcmp(*arg, "wrap")) { 433 tcol->strategy = TABULAR_WRAP; 434 } else if (!strcmp(*arg, "trunc")) { 435 tcol->strategy = TABULAR_TRUNC; 436 } else { 437 die("bad %s", arg[-1]); 438 } 439 } else if (!strcmp(*arg + n, "hide")) { 440 if (!*++arg) die("missing args"); 441 col->hide_out = *arg; 442 } else { 443 goto skip; 444 } 445 } else { 446skip: 447 *dst++ = *arg; 448 } 449 } 450 *dst = NULL; 451 452 if (argv[1]) die("unused argument '%s'", argv[1]); 453 454 dvec_add_back(&cols, colcnt); 455 for (HMAP_ITER(&colmap, iter)) { 456 col = iter.link->value._p; 457 tcol = dvec_at(&cols, col->tabular.user.id); 458 memcpy(tcol, &col->tabular, sizeof(struct tabular_col)); 459 tcol->user.ptr = col; 460 } 461 cfg.columns = dvec_front(&cols); 462 cfg.column_count = cols.len; 463} 464 465int 466main(int argc, const char **argv) 467{ 468 struct tabular_row *rows; 469 struct tabular_stats stats; 470 int rc; 471 472 parse(argc, argv); 473 474 dvec_init(&line, 1, 1024, ga); 475 dvec_init(&input, 1, 1024, ga); 476 dvec_init(&entries, sizeof(size_t), 1, ga); 477 478 rows = NULL; 479 rc = tabular_format(stdout, &cfg, &stats, &rows); 480 if (rc) errx(1, "tabular_format (%i)", rc); 481 482 printf("\n%lu lines, %lu rows", 483 stats.lines_used, stats.rows_displayed); 484 if (stats.rows_truncated) printf(" (rows truncated)"); 485 if (stats.cols_truncated) printf(" (cols truncated)"); 486 printf("\n"); 487 488 tabular_free_rows(&cfg, rows); 489}