table.c (30871B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2020-2026 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <assert.h> 6 #include <errno.h> 7 #include <limits.h> 8 #include <stdarg.h> 9 #include <stddef.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sys/stat.h> 14 #include <unistd.h> 15 16 #include "defs.h" 17 #include "strtonum.h" 18 #include "version.h" 19 20 #define COPYRIGHT \ 21 (" This program is licensed under the terms of GNU GPL v3" \ 22 " or (at your option)\n" \ 23 " any later version. Copyright (C) 2020-2026 Strahinya Radich.\n" \ 24 " See the file LICENSE for exact copyright and license " \ 25 "details.") 26 27 static int* format = NULL; 28 static Alignment* align = NULL; 29 static size_t align_size = 0; 30 static size_t colno = 0; 31 static size_t current_rune_column = 0; 32 static size_t current_table_column = 0; 33 static size_t format_size = 0; 34 static size_t lineno = 0; 35 static size_t tab_length = 8; 36 static size_t table_columns = 0; 37 static ssize_t line_size; 38 static ssize_t outbuf_size; 39 static ssize_t rune_columns = 80; 40 static u32 delimiter = U32(','); 41 static long line_max; 42 static int align_header = 0; 43 static int border_mode = 0; 44 static int current_inner_symbol_set = TABLE_INNER_DOUBLE_SINGLE; 45 static int current_symbol_set = TABLE_SYMBOLS_DOUBLE; 46 static int error_code = ERR_NONE; 47 static int expand_tabs = 0; 48 static int format_value = 0; 49 static int in_sgr = 0; 50 static int msdos = 0; 51 static int no_ansi = 0; 52 static int opause = 0; 53 static int quotes = Q_PROCESS; 54 static char alignment_char = ' '; 55 56 /* clang-format off */ 57 static const int alignments[] = { 58 ['l'] = ALIGN_LEFT, 59 ['c'] = ALIGN_CENTER, 60 ['r'] = ALIGN_RIGHT 61 }; 62 static const struct { 63 int border; 64 int inner; 65 } symbol_sets[] = { 66 [HASH('a','a')] = { .border = TABLE_SYMBOLS_ASCII, 67 .inner = TABLE_INNER_ASCII_ASCII }, 68 [HASH('a','e')] = { .border = TABLE_SYMBOLS_ASCII, 69 .inner = TABLE_INNER_ASCII_EMPTY }, 70 [HASH('d','d')] = { .border = TABLE_SYMBOLS_DOUBLE, 71 .inner = TABLE_INNER_DOUBLE_DOUBLE }, 72 [HASH('d','e')] = { .border = TABLE_SYMBOLS_DOUBLE, 73 .inner = TABLE_INNER_DOUBLE_EMPTY }, 74 [HASH('d','s')] = { .border = TABLE_SYMBOLS_DOUBLE, 75 .inner = TABLE_INNER_DOUBLE_SINGLE }, 76 [HASH('e','a')] = { .border = TABLE_SYMBOLS_EMPTY, 77 .inner = TABLE_INNER_EMPTY_ASCII }, 78 [HASH('e','d')] = { .border = TABLE_SYMBOLS_EMPTY, 79 .inner = TABLE_INNER_EMPTY_DOUBLE }, 80 [HASH('e','e')] = { .border = TABLE_SYMBOLS_EMPTY, 81 .inner = TABLE_INNER_EMPTY_EMPTY }, 82 [HASH('e','s')] = { .border = TABLE_SYMBOLS_EMPTY, 83 .inner = TABLE_INNER_EMPTY_SINGLE }, 84 [HASH('s','d')] = { .border = TABLE_SYMBOLS_SINGLE, 85 .inner = TABLE_INNER_SINGLE_DOUBLE }, 86 [HASH('s','e')] = { .border = TABLE_SYMBOLS_SINGLE, 87 .inner = TABLE_INNER_SINGLE_EMPTY }, 88 [HASH('s','s')] = { .border = TABLE_SYMBOLS_SINGLE, 89 .inner = TABLE_INNER_SINGLE_SINGLE } 90 }; 91 const char* errors[] = { 92 [ERR_NONE] = "", 93 [ERR_NO_CL_ARG_PARAM] = "Option requires an argument: %s", 94 [ERR_BAD_CL_ARG] = "Invalid argument: %s", 95 [ERR_BAD_ALIGN] = "Invalid alignment specification: %s", 96 [ERR_BAD_NUMBER] = "Invalid numeric argument: %s", 97 [ERR_BAD_FORMAT_SPEC] = "Invalid format specification: %s", 98 [ERR_BAD_QUOTES_ARG] = "Invalid quotes specification: %s", 99 [ERR_BAD_SET_SPEC] = "Invalid set specification: %s", 100 [ERR_ISDIR] = "'%s' is a directory", 101 [ERR_NOTREG_FILE] = "'%s' is not a regular file", 102 [ERR_UNMATCHED_QUOTE] = "Unmatched quote", 103 [ERR_STREAM_ERROR] = "Input stream error", 104 [ERR_BINARY_INPUT] = "Input is not text", 105 }; 106 /* clang-format on */ 107 108 static int skip_while_sgr(const u8* s, size_t* char_delta, int also_print, 109 int whole_string); 110 static void warning(const char* fmt, ...); 111 112 static size_t 113 current_column_width(void) 114 { 115 return format ? format[current_table_column] : format_value; 116 } 117 118 static int 119 error(const int code, const char* fmt, ...) 120 { 121 va_list args; 122 char* buf = NULL; 123 assert(fmt != NULL); 124 MALLOC(buf, line_max); 125 va_start(args, fmt); 126 if (vsnprintf(buf, line_max, fmt, args) >= line_max) 127 buf[line_max - 1] = 0; 128 va_end(args); 129 fprintf(stderr, "%s: %s\n", PROGRAMNAME, buf); 130 free(buf); 131 return code; 132 } 133 134 /* Return number of columns based on the input line */ 135 static size_t 136 number_of_columns(const u8* input, u32 delimiter) 137 { 138 const u8* pinput = NULL; 139 u32 uch; 140 size_t ch_len = 0; 141 size_t result = 1; 142 143 assert(input != NULL); 144 pinput = input; 145 while (*pinput) 146 { 147 if (u8_rune_to_u32(&uch, pinput, &ch_len)) 148 { 149 warning("Malformed UTF-8 at position %td: " 150 "from=%02X", 151 pinput - input, *pinput); 152 } 153 if (uch == delimiter) 154 result++; 155 pinput += ch_len; 156 } 157 return result; 158 } 159 160 static int 161 print_aligned(const u8* s, size_t rune_length, size_t to_size, Alignment align, 162 size_t* current_rune_column) 163 { 164 const u8* ps = s; 165 u32 uch; 166 size_t i; 167 size_t ch_len; 168 size_t colwidth = current_column_width(); 169 size_t output_runes = 0; 170 size_t skipped_runes = 0; 171 size_t output_chars = 0; 172 size_t skipped_chars = 0; 173 174 assert((s != NULL) && (current_rune_column != NULL)); 175 if (align == ALIGN_CENTER) 176 for (i = 0; 2 * i + rune_length < to_size; i++) 177 { 178 fputc(alignment_char, stdout); 179 (*current_rune_column)++; 180 } 181 else if (align == ALIGN_RIGHT) 182 for (i = 0; i + rune_length < to_size; i++) 183 { 184 fputc(alignment_char, stdout); 185 (*current_rune_column)++; 186 } 187 if (align == ALIGN_LEFT) 188 { 189 output_runes = 0; 190 while (output_runes != MIN(rune_length, colwidth)) 191 { 192 skip_while_sgr(ps, &skipped_chars, 1, 0); 193 ps += skipped_chars; 194 195 if (!no_ansi && lineno == 0) 196 fputs(ANSI_SGR_BOLD_ON, stdout); 197 198 if (u8_rune_to_u32(&uch, ps, &ch_len)) 199 { 200 warning("Malformed UTF-8 at position %td: " 201 "from=%02X", 202 ps - s, *ps); 203 } 204 205 output_chars = 0; 206 while (output_chars != ch_len) 207 { 208 fputc((char)*ps++, stdout); 209 output_chars++; 210 } 211 output_runes++; 212 } 213 skip_while_sgr(ps, &skipped_chars, 1, 1); 214 } 215 else if (align == ALIGN_CENTER) 216 { 217 skipped_runes = 0; 218 while (2 * skipped_runes + to_size < rune_length) 219 { 220 skip_while_sgr(ps, &skipped_chars, 1, 0); 221 ps += skipped_chars; 222 223 if (u8_rune_to_u32(&uch, ps, &ch_len)) 224 { 225 warning("Malformed UTF-8 at position %td: " 226 "from=%02X", 227 ps - s, *ps); 228 } 229 ps += ch_len; 230 skipped_runes++; 231 } 232 233 output_runes = 0; 234 while (output_runes != MIN(rune_length, colwidth)) 235 { 236 skip_while_sgr(ps, &skipped_chars, 1, 0); 237 ps += skipped_chars; 238 239 if (!no_ansi && lineno == 0) 240 fputs(ANSI_SGR_BOLD_ON, stdout); 241 242 if (u8_rune_to_u32(&uch, ps, &ch_len)) 243 { 244 warning("Malformed UTF-8 at position %td: " 245 "from=%02X", 246 ps - s, *ps); 247 } 248 output_chars = 0; 249 while (output_chars != ch_len) 250 { 251 fputc((char)*ps++, stdout); 252 output_chars++; 253 } 254 output_runes++; 255 } 256 skip_while_sgr(ps, &skipped_chars, 1, 1); 257 } 258 else if (align == ALIGN_RIGHT) 259 { 260 skipped_runes = 0; 261 while (skipped_runes + to_size < rune_length) 262 { 263 skip_while_sgr(ps, &skipped_chars, 1, 0); 264 ps += skipped_chars; 265 266 if (u8_rune_to_u32(&uch, ps, &ch_len)) 267 { 268 warning("Malformed UTF-8 at position %td: " 269 "from=%02X", 270 ps - s, *ps); 271 } 272 ps += ch_len; 273 skipped_runes++; 274 } 275 output_runes = 0; 276 while (output_runes != MIN(rune_length, colwidth)) 277 { 278 skip_while_sgr(ps, &skipped_chars, 1, 0); 279 ps += skipped_chars; 280 281 if (!no_ansi && lineno == 0) 282 fputs(ANSI_SGR_BOLD_ON, stdout); 283 284 if (u8_rune_to_u32(&uch, ps, &ch_len)) 285 { 286 warning("Malformed UTF-8 at position %td: " 287 "from=%02X", 288 ps - s, *ps); 289 } 290 output_chars = 0; 291 while (output_chars != ch_len) 292 { 293 fputc((char)*ps++, stdout); 294 output_chars++; 295 } 296 output_runes++; 297 } 298 skip_while_sgr(ps, &skipped_chars, 1, 1); 299 } 300 (*current_rune_column) += output_runes; 301 return 0; 302 } 303 304 static unsigned int 305 round_div(unsigned int a, unsigned int b) 306 { 307 assert(b != 0); 308 return (a + (b / 2)) / b; 309 } 310 311 static int 312 set_align(const char* arg, Alignment** align, size_t* align_size) 313 { 314 Alignment* palign = NULL; 315 const char* parg = arg; 316 317 assert((arg != NULL) && (align != NULL) && (align_size != NULL)); 318 *align_size = strlen(arg); 319 CALLOC(*align, Alignment, *align_size); 320 palign = *align; 321 while (*parg) 322 { 323 if (!strchr("lcr", *parg)) 324 { 325 free(*align); 326 *align = NULL; 327 *align_size = 0; 328 error_code = ERR_BAD_ALIGN; 329 return 1; 330 } 331 assert((*parg == 'l') || (*parg == 'c') || (*parg == 'r')); 332 *palign++ = alignments[(int)*parg++]; 333 } 334 return 0; 335 } 336 337 static int 338 set_columns(const char* arg, ssize_t* cols) 339 { 340 ssize_t c; 341 int errornum; 342 assert((arg != NULL) && (cols != NULL)); 343 errno = 0; 344 if (!*arg) 345 { 346 error_code = ERR_NO_CL_ARG_PARAM; 347 return 1; 348 } 349 c = strtonumber(arg, -((unsigned long long)SSIZE_MAX + 1), 350 (long long)SSIZE_MAX, &errornum); 351 if (stn_handle_error(arg, -((unsigned long long)SSIZE_MAX + 1), 352 (long long)SSIZE_MAX, errornum)) 353 { 354 error_code = ERR_BAD_NUMBER; 355 return 1; 356 } 357 else 358 *cols = c; 359 return 0; 360 } 361 362 static int 363 set_delimiter(const char* arg, u32* delimiter) 364 { 365 size_t delimiter_len = 0; 366 u32 uch; 367 assert((arg != NULL) && (delimiter != NULL)); 368 if (!u8_rune_to_u32(&uch, (u8*)arg, &delimiter_len)) 369 *delimiter = uch; 370 return 0; 371 } 372 373 static int 374 set_format(const char* arg, int** format, size_t* format_size) 375 { 376 int* pformat = NULL; 377 const char* parg = arg; 378 char* token = NULL; 379 char* ptoken = NULL; 380 size_t token_size = BUF_DEFAULT; 381 int num, errornum; 382 383 assert((arg != NULL) && (format != NULL) && (format_size != NULL)); 384 CALLOC(token, char, token_size); 385 ptoken = token; 386 *format_size = FORMAT_DEFAULT; 387 CALLOC(*format, int, *format_size); 388 pformat = *format; 389 while (*parg) 390 { 391 if (!((*parg >= '0' && *parg <= '9') || *parg == ':')) 392 { 393 free(token); 394 free(*format); 395 *format = NULL; 396 error_code = ERR_BAD_FORMAT_SPEC; 397 return 1; 398 } 399 if (*parg == ':') 400 { 401 if (parg == arg || !*(parg + 1)) 402 { 403 free(token); 404 free(*format); 405 *format = NULL; 406 error_code = ERR_BAD_FORMAT_SPEC; 407 return 2; 408 } 409 *ptoken = 0; 410 errno = 0; 411 num = strtonumber(token, INT_MIN, INT_MAX, &errornum); 412 if (stn_handle_error(token, INT_MIN, INT_MAX, errornum)) 413 { 414 free(token); 415 free(*format); 416 *format = NULL; 417 error_code = ERR_BAD_NUMBER; 418 return 3; 419 } 420 CHECKSET(*format, pformat, *format_size, num); 421 *token = 0; 422 ptoken = token; 423 parg++; 424 } 425 else 426 CHECKCOPY(token, ptoken, token_size, parg); 427 } 428 if (*token) 429 { 430 *ptoken = 0; 431 errno = 0; 432 num = strtonumber(token, INT_MIN, INT_MAX, &errornum); 433 if (stn_handle_error(token, INT_MIN, INT_MAX, errornum)) 434 { 435 free(token); 436 free(*format); 437 *format = NULL; 438 error_code = ERR_BAD_NUMBER; 439 return 3; 440 } 441 CHECKSET(*format, pformat, *format_size, num); 442 } 443 free(token); 444 return 0; 445 } 446 447 static int 448 set_quotes_behavior(const char* arg, int* quotes) 449 { 450 assert((arg != NULL) && (quotes != NULL)); 451 if (!strcmp(arg, "process")) 452 *quotes = Q_PROCESS; 453 else if (!strcmp(arg, "ignore")) 454 *quotes = Q_IGNORE; 455 else 456 { 457 error_code = ERR_BAD_QUOTES_ARG; 458 return 1; 459 } 460 return 0; 461 } 462 463 static int 464 set_symbol_set(char* arg, int* current_symbol_set, int* current_inner_symbol_set) 465 { 466 assert((arg != NULL) && (current_symbol_set != NULL) 467 && (current_inner_symbol_set != NULL)); 468 if (!arg[0] || !arg[1] || arg[2] || !strchr("ades", arg[0]) 469 || !strchr("ades", arg[1])) 470 { 471 error_code = ERR_BAD_SET_SPEC; 472 return 1; 473 } 474 assert((arg[0] == 'a') || (arg[0] == 'd') || (arg[0] == 'e') 475 || (arg[0] == 's') || (arg[1] == 'a') || (arg[1] == 'd') 476 || (arg[1] == 'e') || (arg[1] == 's')); 477 if (!strcmp(arg, "ad") || !strcmp(arg, "as") || !strcmp(arg, "ad") 478 || !strcmp(arg, "as") || !strcmp(arg, "da") 479 || !strcmp(arg, "sa")) 480 { 481 error_code = ERR_BAD_SET_SPEC; 482 return 1; 483 } 484 *current_symbol_set = symbol_sets[HASH(arg[0], arg[1])].border; 485 *current_inner_symbol_set = symbol_sets[HASH(arg[0], arg[1])].inner; 486 return 0; 487 } 488 489 static int 490 skip_while_sgr(const u8* s, size_t* char_delta, int also_print, int whole_string) 491 { 492 const u8* ps = s; 493 assert((s != NULL) && (char_delta != NULL)); 494 *char_delta = 0; 495 while (*ps) 496 { 497 if (*ps != '\033' || *(ps + 1) != '[') 498 { 499 if (whole_string) 500 { 501 ps++; 502 continue; 503 } 504 else 505 return 0; 506 } 507 508 if (also_print) 509 { 510 fputc((char)*ps++, stdout); 511 fputc((char)*ps++, stdout); 512 } 513 else 514 ps += 2; 515 *char_delta += 2; 516 517 while (*ps && *(ps - 1) != 'm') 518 { 519 if (also_print) 520 fputc((char)*ps++, stdout); 521 else 522 ps++; 523 (*char_delta)++; 524 } 525 } 526 return 0; 527 } 528 529 static int 530 startswith(const char* s, const char* what) 531 { 532 assert((s != NULL) && (what != NULL)); 533 while (*s && *s == *what) 534 { 535 if (*s != *what) 536 return 0; 537 s++; 538 what++; 539 } 540 return *what == 0; 541 } 542 543 static int 544 usage(void) 545 { 546 printf("Usage:\t%s -h | --help | -V | --full-version | -v | --version\n" 547 "\t%s [-a <alignment> | --align=<alignment>]" 548 " [-b | --border-mode] [-c <cols> | --columns=<cols>]" 549 " [-D | --align-dot] [-d <delim> | --delimiter=<delim>]" 550 " [-f <format> | --format=<format>] [-H | --align-header]" 551 " [-m | --msdos] [-n | --no-ansi] [-p | --pause]" 552 " [-q <behavior> | --quotes=<behavior>]" 553 " [-s <set> | --symbols=<set>] [-t | --expand-tabs]" 554 " [-y | --yes-ansi] [pathname]\n", 555 PROGRAMNAME, PROGRAMNAME); 556 return 0; 557 } 558 559 static int 560 version(const int full) 561 { 562 printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); 563 if (full) 564 puts(COPYRIGHT); 565 return 0; 566 } 567 568 static void 569 warning(const char* fmt, ...) 570 { 571 va_list args; 572 char* buf = NULL; 573 assert(fmt != NULL); 574 MALLOC(buf, line_max); 575 va_start(args, fmt); 576 if (vsnprintf(buf, line_max, fmt, args) >= line_max) 577 buf[line_max - 1] = 0; 578 va_end(args); 579 fprintf(stderr, "%s: %s\n", PROGRAMNAME, buf); 580 free(buf); 581 } 582 583 static int 584 within_column(size_t column_start, size_t current_rune_column) 585 { 586 return current_rune_column < column_start + current_column_width(); 587 } 588 589 int 590 main(int argc, char** argv) 591 { 592 FILE* input = NULL; 593 struct stat st; 594 Command cmd = CMD_NONE; 595 u32 uch; 596 u8* line = NULL; 597 u8* pline = NULL; 598 u8* outbuf = NULL; 599 u8* poutbuf = NULL; 600 u8* tpoutbuf = NULL; 601 u8* pch_end; 602 u8 u8ch[7]; 603 char* arg; 604 char* filename = NULL; 605 char* sep = NULL; 606 char* progname = NULL; 607 char* eol = NULL; 608 char* nulbyte = NULL; 609 ptrdiff_t outbuf_len; 610 size_t outbufrunelen = 0; 611 size_t ch_len = 0; 612 size_t output_lines = 0; 613 size_t column_start = 0; 614 int in_quote = 0; 615 int possible_nulbyte = 0; 616 int u8len = 0; 617 618 UNUSED(argc); 619 620 #ifdef __OpenBSD__ 621 if (pledge("stdio rpath unveil", NULL) < 0) 622 { 623 perror(PROGRAMNAME ": pledge"); 624 exit(errno); 625 } 626 #endif 627 628 sep = strrchr(argv[0], '/'); 629 progname = argv[0]; 630 if (sep) 631 progname = sep + 1; 632 633 if (!strcmp(progname, "tsvtable")) 634 { 635 delimiter = TAB; 636 quotes = Q_IGNORE; 637 } 638 else if (!strcmp(progname, "tablep")) 639 opause = 1; 640 641 errno = 0; 642 if (!isatty(fileno(stdout))) 643 { 644 if (errno == ENOTTY) 645 no_ansi = 1; 646 else if (errno) 647 { 648 perror(PROGRAMNAME ": isatty"); 649 exit(errno); 650 } 651 } 652 653 errno = 0; 654 line_max = sysconf(_SC_LINE_MAX); 655 if (line_max == -1) 656 { 657 if (errno) 658 { 659 perror(PROGRAMNAME ": sysconf"); 660 exit(errno); 661 } 662 line_max = _POSIX2_LINE_MAX; 663 } 664 do_arg: 665 arg = *++argv; 666 667 if (!arg) 668 goto done_arg; 669 670 if (*arg == '-') 671 { 672 char c; 673 arg++; 674 c = *arg++; 675 if (c == '-') 676 { 677 if (!strcmp(arg, "full-version")) 678 cmd = CMD_FULL_VERSION; 679 else if (!strcmp(arg, "version")) 680 cmd = CMD_VERSION; 681 else if (!strcmp(arg, "align-header")) 682 align_header = 1; 683 else if (!strcmp(arg, "align-dot")) 684 alignment_char = '.'; 685 else if (startswith(arg, "align=")) 686 { 687 arg += strlen("align="); 688 if (set_align(arg, &align, &align_size)) 689 goto main_done; 690 } 691 else if (!strcmp(arg, "border-mode")) 692 border_mode = 1; 693 else if (startswith(arg, "columns=")) 694 { 695 arg += strlen("columns="); 696 if (set_columns(arg, &rune_columns)) 697 goto main_done; 698 } 699 else if (startswith(arg, "delimiter=")) 700 { 701 arg += strlen("delimiter="); 702 if (set_delimiter(arg, &delimiter)) 703 goto main_done; 704 } 705 else if (!strcmp(arg, "expand-tabs")) 706 expand_tabs = 1; 707 else if (startswith(arg, "format=")) 708 { 709 arg += strlen("format="); 710 if (set_format(arg, &format, &format_size)) 711 goto main_done; 712 } 713 else if (!strcmp(arg, "msdos")) 714 msdos = 1; 715 else if (!strcmp(arg, "no-ansi")) 716 no_ansi = 1; 717 else if (!strcmp(arg, "pause")) 718 opause = 1; 719 else if (startswith(arg, "quotes=")) 720 { 721 arg += strlen("quotes="); 722 if (set_quotes_behavior(arg, "es)) 723 goto main_done; 724 } 725 else if (startswith(arg, "symbols=")) 726 { 727 arg += strlen("symbols="); 728 if (set_symbol_set(arg, ¤t_symbol_set, 729 ¤t_inner_symbol_set)) 730 goto main_done; 731 } 732 else if (!strcmp(arg, "yes-ansi")) 733 no_ansi = 0; 734 else if (!strcmp(arg, "help")) 735 { 736 usage(); 737 goto main_done; 738 } 739 else 740 { 741 (void)usage(); 742 error_code = ERR_BAD_CL_ARG; 743 goto main_done; 744 } 745 } 746 else 747 { 748 switch (c) 749 { 750 case 'a': 751 cmd = CMD_ALIGN; 752 break; 753 case 'b': 754 border_mode = 1; 755 break; 756 case 'c': 757 cmd = CMD_COLUMNS; 758 break; 759 case 'D': 760 alignment_char = '.'; 761 break; 762 case 'd': 763 cmd = CMD_DELIMITER; 764 break; 765 case 'f': 766 cmd = CMD_FORMAT; 767 break; 768 case 'H': 769 align_header = 1; 770 break; 771 case 'h': 772 usage(); 773 goto main_done; 774 case 'm': 775 msdos = 1; 776 break; 777 case 'n': 778 no_ansi = 1; 779 break; 780 case 'p': 781 opause = 1; 782 break; 783 case 'q': 784 cmd = CMD_QUOTES; 785 break; 786 case 's': 787 cmd = CMD_SYMBOLS; 788 break; 789 case 't': 790 expand_tabs = 1; 791 break; 792 case 'V': 793 cmd = CMD_FULL_VERSION; 794 break; 795 case 'v': 796 cmd = CMD_VERSION; 797 break; 798 case 'y': 799 no_ansi = 0; 800 break; 801 default: 802 (void)usage(); 803 error_code = ERR_BAD_CL_ARG; 804 goto main_done; 805 } 806 /* Gobble up the rest of the arg ("-d:" etc) */ 807 if (*arg) 808 goto continue_current_arg; 809 else 810 goto do_arg; 811 } 812 } 813 else 814 { 815 continue_current_arg: 816 if (cmd == CMD_ALIGN) 817 { 818 if (set_align(arg, &align, &align_size)) 819 goto main_done; 820 } 821 else if (cmd == CMD_COLUMNS) 822 { 823 if (set_columns(arg, &rune_columns)) 824 goto main_done; 825 } 826 else if (cmd == CMD_DELIMITER) 827 { 828 if (set_delimiter(arg, &delimiter)) 829 goto main_done; 830 } 831 else if (cmd == CMD_FORMAT) 832 { 833 if (set_format(arg, &format, &format_size)) 834 goto main_done; 835 } 836 else if (cmd == CMD_QUOTES) 837 { 838 if (set_quotes_behavior(arg, "es)) 839 goto main_done; 840 } 841 else if (cmd == CMD_SYMBOLS) 842 { 843 if (set_symbol_set(arg, ¤t_symbol_set, 844 ¤t_inner_symbol_set)) 845 goto main_done; 846 } 847 else if (filename) 848 warning("Additional argument ignored: '%s'", arg); 849 else 850 filename = arg; 851 cmd = CMD_NONE; 852 } 853 goto do_arg; 854 855 done_arg: 856 if (cmd == CMD_COLUMNS || cmd == CMD_DELIMITER || cmd == CMD_FORMAT 857 || cmd == CMD_SYMBOLS || cmd == CMD_QUOTES) 858 { 859 error_code = ERR_NO_CL_ARG_PARAM; 860 goto main_done; 861 } 862 else if (cmd == CMD_FULL_VERSION) 863 return version(1); 864 else if (cmd == CMD_VERSION) 865 return version(0); 866 867 if (filename) 868 { 869 #ifdef __OpenBSD__ 870 if (unveil(filename, "r") < 0) 871 { 872 perror(PROGRAMNAME ": unveil"); 873 exit(errno); 874 } 875 if (unveil(NULL, NULL) < 0) 876 { 877 perror(PROGRAMNAME ": unveil"); 878 exit(errno); 879 } 880 #endif 881 errno = 0; 882 if (stat(filename, &st) < 0) 883 { 884 perror(PROGRAMNAME ": stat"); 885 exit(errno); 886 } 887 if (S_ISDIR(st.st_mode)) 888 { 889 error_code = ERR_ISDIR; 890 goto main_done; 891 } 892 if (!S_ISREG(st.st_mode)) 893 { 894 error_code = ERR_NOTREG_FILE; 895 goto main_done; 896 } 897 errno = 0; 898 input = fopen(filename, "r"); 899 if (!input) 900 goto main_done; 901 } 902 else 903 input = stdin; 904 905 line_size = line_max; 906 CALLOC(line, u8, line_size); 907 outbuf_size = line_max; 908 CALLOC(outbuf, u8, outbuf_size); 909 pline = line; 910 possible_nulbyte = 0; 911 912 do_input: 913 if (feof(input)) 914 goto done_input; 915 916 if (!fgets((char*)pline, line_size - (pline - line), input)) 917 { 918 if (ferror(input)) 919 { 920 error_code = ERR_STREAM_ERROR; 921 goto main_done; 922 } 923 goto done_input; 924 } 925 926 if (possible_nulbyte) 927 { 928 error_code = ERR_BINARY_INPUT; 929 goto main_done; 930 } 931 eol = strchr((char*)pline, '\n'); 932 if (eol) 933 { 934 if (msdos && *pline != '\n' && *(eol - 1) == '\r') 935 *(eol - 1) = 0; 936 *eol = 0; 937 } 938 939 nulbyte = strchr((char*)pline, 0); 940 941 /* We don't know if the NUL byte not being equal to 942 * (possibly NULL) eol is the result of EOF or not 943 */ 944 if (nulbyte && (nulbyte != eol)) 945 possible_nulbyte = 1; 946 947 if (!eol) 948 { 949 line_size += line_max; 950 REALLOC(line, u8, line_size); 951 outbuf_size += line_max; 952 REALLOC(outbuf, u8, outbuf_size); 953 pline = line + line_size - line_max - 1; 954 goto do_input; 955 } 956 957 if (!*line && lineno != 0) 958 goto empty_line; 959 960 in_quote = 0; 961 pline = line; 962 poutbuf = outbuf; 963 outbufrunelen = 0; 964 colno = 0; 965 current_rune_column = 0; 966 967 /* Top border */ 968 if (lineno != 0) 969 goto not_first; 970 971 if (!border_mode) 972 table_columns = number_of_columns(line, delimiter); 973 else 974 table_columns = 1; 975 976 /* Sanity check */ 977 if (rune_columns < 0 || rune_columns < (ssize_t)(2 * table_columns + 3)) 978 { 979 warning("Columns adjusted to a reasonable minimum, %u", 980 2 * table_columns + 3); 981 rune_columns = 2 * table_columns + 3; 982 } 983 assert(rune_columns >= (ssize_t)(2 * table_columns + 3)); 984 985 if (format && !border_mode) 986 { 987 int* pformat = format; 988 int format_sum = 0; 989 while (*pformat && pformat - format < (ssize_t)table_columns) 990 format_sum += *pformat++; 991 pformat = format; 992 while (*pformat && format_sum) 993 { 994 *pformat = round_div((rune_columns - table_columns - 2) 995 * (*pformat), 996 format_sum); 997 assert(*pformat > 0); 998 pformat++; 999 } 1000 } 1001 else 1002 { 1003 format_value 1004 = (rune_columns - table_columns - 2) / table_columns; 1005 1006 assert(format_value >= 1); 1007 } 1008 1009 current_table_column = 0; 1010 column_start = 0; 1011 1012 do_column: 1013 if (current_table_column >= table_columns) 1014 goto done_columns; 1015 1016 if (current_rune_column == 0) 1017 { 1018 u8len = u32_rune_to_u8(u8ch, 1019 table_symbols[current_symbol_set][0]); 1020 u8ch[u8len] = 0; 1021 fputs((const char*)u8ch, stdout); 1022 column_start++; 1023 } 1024 else if (!within_column(column_start, current_rune_column)) 1025 { 1026 if (current_table_column == table_columns - 1) 1027 { 1028 u8len = u32_rune_to_u8(u8ch, 1029 table_symbols[current_symbol_set][2]); 1030 u8ch[u8len] = 0; 1031 fputs((const char*)u8ch, stdout); 1032 } 1033 else 1034 { 1035 /* clang-format off */ 1036 u8len = u32_rune_to_u8(u8ch, table_inner_symbols[ 1037 current_inner_symbol_set][0]); 1038 /* clang-format on */ 1039 u8ch[u8len] = 0; 1040 fputs((const char*)u8ch, stdout); 1041 column_start++; 1042 if (format) 1043 column_start 1044 += *(format + current_table_column); 1045 else 1046 column_start += format_value; 1047 } 1048 current_table_column++; 1049 } 1050 else 1051 { 1052 u8len = u32_rune_to_u8(u8ch, 1053 table_symbols[current_symbol_set][1]); 1054 u8ch[u8len] = 0; 1055 fputs((const char*)u8ch, stdout); 1056 } 1057 current_rune_column++; 1058 goto do_column; 1059 1060 done_columns: 1061 puts(""); 1062 output_lines++; 1063 goto not_first; 1064 1065 empty_line: 1066 current_table_column = 0; 1067 current_rune_column = 0; 1068 column_start = 0; 1069 u8len = u32_rune_to_u8(u8ch, table_symbols[current_symbol_set][3]); 1070 u8ch[u8len] = 0; 1071 fputs((const char*)u8ch, stdout); 1072 while (current_table_column + 1 < table_columns) 1073 { 1074 while (within_column(column_start, current_rune_column)) 1075 { 1076 fputc(alignment_char, stdout); 1077 current_rune_column++; 1078 } 1079 /* clang-format off */ 1080 u8len = u32_rune_to_u8(u8ch, 1081 table_inner_symbols[current_inner_symbol_set][1]); 1082 /* clang-format on */ 1083 u8ch[u8len] = 0; 1084 fputs((const char*)u8ch, stdout); 1085 column_start++; 1086 if (format) 1087 column_start += *(format + current_table_column); 1088 else 1089 column_start += format_value; 1090 current_table_column++; 1091 current_rune_column++; 1092 } 1093 while (within_column(column_start, current_rune_column)) 1094 { 1095 fputc(alignment_char, stdout); 1096 current_rune_column++; 1097 } 1098 u8len = u32_rune_to_u8(u8ch, table_symbols[current_symbol_set][5]); 1099 u8ch[u8len] = 0; 1100 puts((const char*)u8ch); 1101 pline = line; 1102 goto do_input; 1103 1104 not_first: 1105 /* Inner rows */ 1106 current_table_column = 0; 1107 current_rune_column = 0; 1108 column_start = 0; 1109 in_sgr = 0; 1110 1111 u8len = u32_rune_to_u8(u8ch, table_symbols[current_symbol_set][3]); 1112 u8ch[u8len] = 0; 1113 fputs((const char*)u8ch, stdout); 1114 1115 do_line: 1116 if (!pline || !*pline) 1117 { 1118 if (in_quote) 1119 { 1120 puts(""); 1121 error_code = ERR_UNMATCHED_QUOTE; 1122 goto main_done; 1123 } 1124 else 1125 goto done_line; 1126 } 1127 1128 if (in_quote && *pline == '"' && *(pline + 1) == '"') 1129 { 1130 pline++; 1131 colno++; 1132 goto continue_line; 1133 } 1134 else if (quotes == Q_PROCESS && *pline == '"') 1135 { 1136 in_quote = !in_quote; 1137 pline++; 1138 colno++; 1139 goto do_line; 1140 } 1141 1142 continue_line: 1143 outbuf_len = poutbuf - outbuf; 1144 ENSURE_SIZE(outbuf, tpoutbuf, outbuf_size, 1145 (ssize_t)(poutbuf - outbuf + sizeof((u8*)ANSI_SGR_BOLD_OFF)), 1146 (ssize_t)(outbuf_size + sizeof((u8*)ANSI_SGR_BOLD_OFF)), u8); 1147 poutbuf = outbuf + outbuf_len; 1148 if (u8_rune_to_u32(&uch, pline, &ch_len)) 1149 { 1150 warning("Malformed UTF-8 at position %td: " 1151 "from=%02X", 1152 pline - line, *pline); 1153 } 1154 if (uch == U32('\033') && *(pline + 1) == '[') 1155 { 1156 in_sgr = 1; 1157 if (!no_ansi) 1158 { 1159 *poutbuf++ = *pline; 1160 *poutbuf = 0; 1161 } 1162 pline++; 1163 colno++; 1164 } 1165 else if (uch == U32('m')) 1166 { 1167 if (!no_ansi || !in_sgr) 1168 *poutbuf++ = *pline; 1169 if (!in_sgr) 1170 outbufrunelen++; 1171 in_sgr = 0; 1172 pline++; 1173 colno++; 1174 } 1175 else if (in_sgr) 1176 { 1177 if (!no_ansi) 1178 { 1179 pch_end = pline + ch_len; 1180 while (pline != pch_end) 1181 { 1182 *poutbuf++ = *pline++; 1183 *poutbuf = 0; 1184 } 1185 } 1186 else 1187 pline += ch_len; 1188 colno += ch_len; 1189 } 1190 else if (uch == delimiter && !in_quote 1191 && current_table_column < table_columns - 1) 1192 { 1193 size_t colwidth = current_column_width(); 1194 1195 if (!no_ansi && lineno == 0) 1196 snprintf((char*)poutbuf, sizeof ANSI_SGR_BOLD_OFF, "%s", 1197 ANSI_SGR_BOLD_OFF); 1198 1199 print_aligned(outbuf, outbufrunelen, colwidth, 1200 (lineno == 0 && !align_header) 1201 ? ALIGN_LEFT 1202 : (current_table_column < align_size 1203 ? align[current_table_column] 1204 : ALIGN_LEFT), 1205 ¤t_rune_column); 1206 1207 while (within_column(column_start, current_rune_column)) 1208 { 1209 fputc(alignment_char, stdout); 1210 current_rune_column++; 1211 } 1212 1213 *outbuf = 0; 1214 poutbuf = outbuf; 1215 outbufrunelen = 0; 1216 1217 column_start++; 1218 if (format) 1219 column_start += *(format + current_table_column); 1220 else 1221 column_start += format_value; 1222 if (current_table_column != table_columns - 1) 1223 { 1224 /* clang-format off */ 1225 u8len = u32_rune_to_u8(u8ch, table_inner_symbols[ 1226 current_inner_symbol_set][1]); 1227 /* clang-format on */ 1228 u8ch[u8len] = 0; 1229 fputs((const char*)u8ch, stdout); 1230 current_rune_column++; 1231 current_table_column++; 1232 } 1233 1234 if (!no_ansi && lineno == 0) 1235 poutbuf += snprintf((char*)poutbuf, 1236 sizeof ANSI_SGR_BOLD_ON, "%s", 1237 ANSI_SGR_BOLD_ON); 1238 1239 pline += ch_len; 1240 colno += ch_len; 1241 } 1242 else if (uch == TAB && expand_tabs) 1243 { 1244 while ((current_rune_column + outbufrunelen) % tab_length != 0) 1245 { 1246 *poutbuf++ = alignment_char; 1247 *poutbuf = 0; 1248 outbufrunelen++; 1249 } 1250 pline++; 1251 colno++; 1252 } 1253 else 1254 { 1255 pch_end = pline + ch_len; 1256 while (pline != pch_end) 1257 { 1258 *poutbuf++ = *pline++; 1259 *poutbuf = 0; 1260 } 1261 outbufrunelen++; 1262 colno += ch_len; 1263 } 1264 goto do_line; 1265 1266 done_line: 1267 if (!no_ansi && lineno == 0) 1268 fputs(ANSI_SGR_BOLD_OFF, stdout); 1269 1270 if (*outbuf) 1271 { 1272 size_t colwidth = format ? *(format + current_table_column) 1273 : format_value; 1274 1275 assert(colwidth != 0); 1276 1277 if (!no_ansi && lineno == 0) 1278 snprintf((char*)poutbuf, sizeof ANSI_SGR_BOLD_OFF, "%s", 1279 ANSI_SGR_BOLD_OFF); 1280 1281 print_aligned(outbuf, outbufrunelen, colwidth, 1282 (lineno == 0 && !align_header) 1283 ? ALIGN_LEFT 1284 : (current_table_column < align_size 1285 ? align[current_table_column] 1286 : ALIGN_LEFT), 1287 ¤t_rune_column); 1288 1289 *outbuf = 0; 1290 } 1291 while (current_table_column < table_columns - 1) 1292 { 1293 while (within_column(column_start, current_rune_column)) 1294 { 1295 fputc(alignment_char, stdout); 1296 current_rune_column++; 1297 } 1298 u8len = u32_rune_to_u8(u8ch, 1299 table_inner_symbols[current_inner_symbol_set][1]); 1300 u8ch[u8len] = 0; 1301 fputs((const char*)u8ch, stdout); 1302 column_start++; 1303 if (format) 1304 column_start += *(format + current_table_column); 1305 else 1306 column_start += format_value; 1307 current_table_column++; 1308 current_rune_column++; 1309 } 1310 while (within_column(column_start, current_rune_column)) 1311 { 1312 fputc(alignment_char, stdout); 1313 current_rune_column++; 1314 } 1315 1316 u8len = u32_rune_to_u8(u8ch, table_symbols[current_symbol_set][5]); 1317 u8ch[u8len] = 0; 1318 puts((const char*)u8ch); 1319 1320 output_lines++; 1321 lineno++; 1322 pline = line; 1323 goto do_input; 1324 1325 done_input: 1326 free(line); 1327 free(outbuf); 1328 1329 /* Bottom border */ 1330 if (!output_lines) 1331 goto skip_bottom_border; 1332 1333 current_table_column = 0; 1334 current_rune_column = 0; 1335 column_start = 0; 1336 1337 do_bottom_border: 1338 if (current_table_column >= table_columns) 1339 goto done_bottom_border; 1340 1341 if (current_rune_column == 0) 1342 { 1343 u8len = u32_rune_to_u8(u8ch, 1344 table_symbols[current_symbol_set][6]); 1345 column_start++; 1346 } 1347 else if (!within_column(column_start, current_rune_column)) 1348 { 1349 if (current_table_column == table_columns - 1) 1350 { 1351 u8len = u32_rune_to_u8(u8ch, 1352 table_symbols[current_symbol_set][8]); 1353 current_table_column++; 1354 } 1355 else 1356 { 1357 u8len = u32_rune_to_u8(u8ch, 1358 table_inner_symbols[current_inner_symbol_set][2]); 1359 column_start++; 1360 if (format) 1361 column_start 1362 += *(format + current_table_column); 1363 else 1364 column_start += format_value; 1365 current_table_column++; 1366 } 1367 } 1368 else 1369 u8len = u32_rune_to_u8(u8ch, 1370 table_symbols[current_symbol_set][7]); 1371 1372 u8ch[u8len] = 0; 1373 fputs((const char*)u8ch, stdout); 1374 current_rune_column++; 1375 goto do_bottom_border; 1376 1377 done_bottom_border: 1378 puts(""); 1379 output_lines++; 1380 1381 skip_bottom_border: 1382 main_done: 1383 if (errno) 1384 perror(PROGRAMNAME); 1385 1386 free(format); 1387 free(align); 1388 if (opause) 1389 { 1390 char* buf = NULL; 1391 MALLOC(buf, line_max); 1392 puts("Press Enter to continue: "); 1393 fflush(stdout); 1394 clearerr(stdin); 1395 fgets(buf, line_max, stdin); 1396 free(buf); 1397 } 1398 if (input) 1399 fclose(input); 1400 1401 switch (error_code) 1402 { 1403 case ERR_NONE: 1404 break; 1405 case ERR_NO_CL_ARG_PARAM: 1406 case ERR_BAD_CL_ARG: 1407 error(error_code, errors[error_code], *argv); 1408 break; 1409 case ERR_ISDIR: 1410 case ERR_NOTREG_FILE: 1411 error(error_code, errors[error_code], filename); 1412 break; 1413 case ERR_BAD_ALIGN: 1414 case ERR_BAD_NUMBER: 1415 case ERR_BAD_FORMAT_SPEC: 1416 case ERR_BAD_QUOTES_ARG: 1417 case ERR_BAD_SET_SPEC: 1418 error(error_code, errors[error_code], arg); 1419 break; 1420 default: 1421 error(error_code, errors[error_code]); 1422 break; 1423 } 1424 return errno ? errno 1425 : (error_code ? CUSTOM_ERROR_START + error_code : 0); 1426 }