1 /**************************************************************************** 2 * Copyright (c) 2008-2012,2013 Free Software Foundation, Inc. * 3 * * 4 * Permission is hereby granted, free of charge, to any person obtaining a * 5 * copy of this software and associated documentation files (the * 6 * "Software"), to deal in the Software without restriction, including * 7 * without limitation the rights to use, copy, modify, merge, publish, * 8 * distribute, distribute with modifications, sublicense, and/or sell * 9 * copies of the Software, and to permit persons to whom the Software is * 10 * furnished to do so, subject to the following conditions: * 11 * * 12 * The above copyright notice and this permission notice shall be included * 13 * in all copies or substantial portions of the Software. * 14 * * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 22 * * 23 * Except as contained in this notice, the name(s) of the above copyright * 24 * holders shall not be used in advertising or otherwise to promote the * 25 * sale, use or other dealings in this Software without prior written * 26 * authorization. * 27 ****************************************************************************/ 28 29 /**************************************************************************** 30 * Author: Thomas E. Dickey 2008 * 31 ****************************************************************************/ 32 33 /* 34 * tabs.c -- set terminal hard-tabstops 35 */ 36 37 #define USE_LIBTINFO 38 #include <progs.priv.h> 39 40 MODULE_ID("$Id: tabs.c,v 1.34 2013/06/11 08:18:27 tom Exp $") 41 42 static void usage(void) GCC_NORETURN; 43 44 static char *prg_name; 45 static int max_cols; 46 47 static void 48 failed(const char *s) 49 { 50 perror(s); 51 ExitProgram(EXIT_FAILURE); 52 } 53 54 static int 55 putch(int c) 56 { 57 return putchar(c); 58 } 59 60 static void 61 do_tabs(int *tab_list) 62 { 63 int last = 1; 64 int stop; 65 66 putchar('\r'); 67 while ((stop = *tab_list++) > 0) { 68 if (last < stop) { 69 while (last++ < stop) { 70 if (last > max_cols) 71 break; 72 putchar(' '); 73 } 74 } 75 if (stop <= max_cols) { 76 tputs(tparm(set_tab, stop), 1, putch); 77 last = stop; 78 } else { 79 break; 80 } 81 } 82 putchar('\n'); 83 } 84 85 static int * 86 decode_tabs(const char *tab_list) 87 { 88 int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols); 89 int n = 0; 90 int value = 0; 91 int prior = 0; 92 int ch; 93 94 if (result == 0) 95 failed("decode_tabs"); 96 97 while ((ch = *tab_list++) != '\0') { 98 if (isdigit(UChar(ch))) { 99 value *= 10; 100 value += (ch - '0'); 101 } else if (ch == ',') { 102 result[n] = value + prior; 103 if (n > 0 && result[n] <= result[n - 1]) { 104 fprintf(stderr, 105 "%s: tab-stops are not in increasing order: %d %d\n", 106 prg_name, value, result[n - 1]); 107 free(result); 108 result = 0; 109 break; 110 } 111 ++n; 112 value = 0; 113 prior = 0; 114 } else if (ch == '+') { 115 if (n) 116 prior = result[n - 1]; 117 } 118 } 119 120 if (result != 0) { 121 /* 122 * If there is only one value, then it is an option such as "-8". 123 */ 124 if ((n == 0) && (value > 0)) { 125 int step = value; 126 value = 1; 127 while (n < max_cols - 1) { 128 result[n++] = value; 129 value += step; 130 } 131 } 132 133 /* 134 * Add the last value, if any. 135 */ 136 result[n++] = value + prior; 137 result[n] = 0; 138 } 139 140 return result; 141 } 142 143 static void 144 print_ruler(int *tab_list) 145 { 146 int last = 0; 147 int stop; 148 int n; 149 150 /* first print a readable ruler */ 151 for (n = 0; n < max_cols; n += 10) { 152 int ch = 1 + (n / 10); 153 char buffer[20]; 154 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 155 "----+----%c", 156 ((ch < 10) 157 ? (ch + '0') 158 : (ch + 'A' - 10))); 159 printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer); 160 } 161 putchar('\n'); 162 163 /* now, print '*' for each stop */ 164 for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) { 165 stop = tab_list[n]; 166 while (++last < stop) { 167 if (last <= max_cols) { 168 putchar('-'); 169 } else { 170 break; 171 } 172 } 173 if (last <= max_cols) { 174 putchar('*'); 175 last = stop; 176 } else { 177 break; 178 } 179 } 180 while (++last <= max_cols) 181 putchar('-'); 182 putchar('\n'); 183 } 184 185 /* 186 * Write an '*' on each tabstop, to demonstrate whether it lines up with the 187 * ruler. 188 */ 189 static void 190 write_tabs(int *tab_list) 191 { 192 int stop; 193 194 while ((stop = *tab_list++) > 0 && stop <= max_cols) { 195 fputs((stop == 1) ? "*" : "\t*", stdout); 196 }; 197 /* also show a tab _past_ the stops */ 198 if (stop < max_cols) 199 fputs("\t+", stdout); 200 putchar('\n'); 201 } 202 203 /* 204 * Trim leading/trailing blanks, as well as blanks after a comma. 205 * Convert embedded blanks to commas. 206 */ 207 static char * 208 trimmed_tab_list(const char *source) 209 { 210 char *result = strdup(source); 211 int ch, j, k, last; 212 213 if (result != 0) { 214 for (j = k = last = 0; result[j] != 0; ++j) { 215 ch = UChar(result[j]); 216 if (isspace(ch)) { 217 if (last == '\0') { 218 continue; 219 } else if (isdigit(last) || last == ',') { 220 ch = ','; 221 } 222 } else if (ch == ',') { 223 ; 224 } else { 225 if (last == ',') 226 result[k++] = (char) last; 227 result[k++] = (char) ch; 228 } 229 last = ch; 230 } 231 result[k] = '\0'; 232 } 233 return result; 234 } 235 236 static bool 237 comma_is_needed(const char *source) 238 { 239 bool result = FALSE; 240 241 if (source != 0) { 242 size_t len = strlen(source); 243 if (len != 0) 244 result = (source[len - 1] != ','); 245 } else { 246 result = FALSE; 247 } 248 return result; 249 } 250 251 /* 252 * Add a command-line parameter to the tab-list. It can be blank- or comma- 253 * separated (or a mixture). For simplicity, empty tabs are ignored, e.g., 254 * tabs 1,,6,11 255 * tabs 1,6,11 256 * are treated the same. 257 */ 258 static const char * 259 add_to_tab_list(char **append, const char *value) 260 { 261 char *result = *append; 262 char *copied = trimmed_tab_list(value); 263 264 if (copied != 0 && *copied != '\0') { 265 const char *comma = ","; 266 size_t need = 1 + strlen(copied); 267 268 if (*copied == ',') 269 comma = ""; 270 else if (!comma_is_needed(*append)) 271 comma = ""; 272 273 need += strlen(comma); 274 if (*append != 0) 275 need += strlen(*append); 276 277 result = malloc(need); 278 if (result == 0) 279 failed("add_to_tab_list"); 280 281 *result = '\0'; 282 if (*append != 0) { 283 _nc_STRCPY(result, *append, need); 284 free(*append); 285 } 286 _nc_STRCAT(result, comma, need); 287 _nc_STRCAT(result, copied, need); 288 289 *append = result; 290 } 291 return result; 292 } 293 294 /* 295 * Check for illegal characters in the tab-list. 296 */ 297 static bool 298 legal_tab_list(const char *tab_list) 299 { 300 bool result = TRUE; 301 302 if (tab_list != 0 && *tab_list != '\0') { 303 if (comma_is_needed(tab_list)) { 304 int n, ch; 305 for (n = 0; tab_list[n] != '\0'; ++n) { 306 ch = UChar(tab_list[n]); 307 if (!(isdigit(ch) || ch == ',' || ch == '+')) { 308 fprintf(stderr, 309 "%s: unexpected character found '%c'\n", 310 prg_name, ch); 311 result = FALSE; 312 break; 313 } 314 } 315 } else { 316 fprintf(stderr, "%s: trailing comma found '%s'\n", prg_name, tab_list); 317 result = FALSE; 318 } 319 } else { 320 fprintf(stderr, "%s: no tab-list given\n", prg_name); 321 result = FALSE; 322 } 323 return result; 324 } 325 326 static char * 327 skip_list(char *value) 328 { 329 while (*value != '\0' && 330 (isdigit(UChar(*value)) || 331 isspace(UChar(*value)) || 332 strchr("+,", UChar(*value)) != 0)) { 333 ++value; 334 } 335 return value; 336 } 337 338 static void 339 usage(void) 340 { 341 static const char *msg[] = 342 { 343 "Usage: tabs [options] [tabstop-list]" 344 ,"" 345 ,"Options:" 346 ," -0 reset tabs" 347 ," -8 set tabs to standard interval" 348 ," -a Assembler, IBM S/370, first format" 349 ," -a2 Assembler, IBM S/370, second format" 350 ," -c COBOL, normal format" 351 ," -c2 COBOL compact format" 352 ," -c3 COBOL compact format extended" 353 ," -d debug (show ruler with expected/actual tab positions)" 354 ," -f FORTRAN" 355 ," -n no-op (do not modify terminal settings)" 356 ," -p PL/I" 357 ," -s SNOBOL" 358 ," -u UNIVAC 1100 Assembler" 359 ," -T name use terminal type 'name'" 360 ," -V print version" 361 ,"" 362 ,"A tabstop-list is an ordered list of column numbers, e.g., 1,11,21" 363 ,"or 1,+10,+10 which is the same." 364 }; 365 unsigned n; 366 367 fflush(stdout); 368 for (n = 0; n < SIZEOF(msg); ++n) { 369 fprintf(stderr, "%s\n", msg[n]); 370 } 371 ExitProgram(EXIT_FAILURE); 372 } 373 374 int 375 main(int argc, char *argv[]) 376 { 377 int rc = EXIT_FAILURE; 378 bool debug = FALSE; 379 bool no_op = FALSE; 380 int n, ch; 381 NCURSES_CONST char *term_name = 0; 382 char *append = 0; 383 const char *tab_list = 0; 384 385 prg_name = _nc_rootname(argv[0]); 386 387 if ((term_name = getenv("TERM")) == 0) 388 term_name = "ansi+tabs"; 389 390 /* cannot use getopt, since some options are two-character */ 391 for (n = 1; n < argc; ++n) { 392 char *option = argv[n]; 393 switch (option[0]) { 394 case '-': 395 while ((ch = *++option) != '\0') { 396 switch (ch) { 397 case 'a': 398 switch (*++option) { 399 default: 400 case '\0': 401 tab_list = "1,10,16,36,72"; 402 option--; 403 /* Assembler, IBM S/370, first format */ 404 break; 405 case '2': 406 tab_list = "1,10,16,40,72"; 407 /* Assembler, IBM S/370, second format */ 408 break; 409 } 410 break; 411 case 'c': 412 switch (*++option) { 413 default: 414 case '\0': 415 tab_list = "1,8,12,16,20,55"; 416 option--; 417 /* COBOL, normal format */ 418 break; 419 case '2': 420 tab_list = "1,6,10,14,49"; 421 /* COBOL compact format */ 422 break; 423 case '3': 424 tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67"; 425 /* COBOL compact format extended */ 426 break; 427 } 428 break; 429 case 'd': /* ncurses extension */ 430 debug = TRUE; 431 break; 432 case 'f': 433 tab_list = "1,7,11,15,19,23"; 434 /* FORTRAN */ 435 break; 436 case 'n': /* ncurses extension */ 437 no_op = TRUE; 438 break; 439 case 'p': 440 tab_list = "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61"; 441 /* PL/I */ 442 break; 443 case 's': 444 tab_list = "1,10,55"; 445 /* SNOBOL */ 446 break; 447 case 'u': 448 tab_list = "1,12,20,44"; 449 /* UNIVAC 1100 Assembler */ 450 break; 451 case 'T': 452 ++n; 453 if (*++option != '\0') { 454 term_name = option; 455 } else { 456 term_name = argv[n++]; 457 option--; 458 } 459 option += ((int) strlen(option)) - 1; 460 continue; 461 case 'V': 462 puts(curses_version()); 463 ExitProgram(EXIT_SUCCESS); 464 default: 465 if (isdigit(UChar(*option))) { 466 char *copy = strdup(option); 467 *skip_list(copy) = '\0'; 468 tab_list = copy; 469 option = skip_list(option) - 1; 470 } else { 471 usage(); 472 } 473 break; 474 } 475 } 476 break; 477 case '+': 478 while ((ch = *++option) != '\0') { 479 switch (ch) { 480 case 'm': 481 /* 482 * The "+mXXX" option is unimplemented because only the long-obsolete 483 * att510d implements smgl, which is needed to support 484 * this option. 485 */ 486 break; 487 default: 488 /* special case of relative stops separated by spaces? */ 489 if (option == argv[n] + 1) { 490 tab_list = add_to_tab_list(&append, argv[n]); 491 } 492 break; 493 } 494 } 495 break; 496 default: 497 if (append != 0) { 498 if (tab_list != (const char *) append) { 499 /* one of the predefined options was used */ 500 free(append); 501 append = 0; 502 } 503 } 504 tab_list = add_to_tab_list(&append, option); 505 break; 506 } 507 } 508 509 setupterm(term_name, STDOUT_FILENO, (int *) 0); 510 511 max_cols = (columns > 0) ? columns : 80; 512 513 if (!VALID_STRING(clear_all_tabs)) { 514 fprintf(stderr, 515 "%s: terminal type '%s' cannot reset tabs\n", 516 prg_name, term_name); 517 } else if (!VALID_STRING(set_tab)) { 518 fprintf(stderr, 519 "%s: terminal type '%s' cannot set tabs\n", 520 prg_name, term_name); 521 } else if (legal_tab_list(tab_list)) { 522 int *list = decode_tabs(tab_list); 523 524 if (!no_op) 525 tputs(clear_all_tabs, 1, putch); 526 527 if (list != 0) { 528 if (!no_op) 529 do_tabs(list); 530 if (debug) { 531 fflush(stderr); 532 printf("tabs %s\n", tab_list); 533 print_ruler(list); 534 write_tabs(list); 535 } 536 free(list); 537 } else if (debug) { 538 fflush(stderr); 539 printf("tabs %s\n", tab_list); 540 } 541 rc = EXIT_SUCCESS; 542 } 543 if (append != 0) 544 free(append); 545 ExitProgram(rc); 546 } 547