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