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