ncurses 5.9 - patch 20121026
[ncurses.git] / progs / dump_entry.c
1 /****************************************************************************
2  * Copyright (c) 1998-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: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996 on                 *
33  ****************************************************************************/
34
35 #define __INTERNAL_CAPS_VISIBLE
36 #include <progs.priv.h>
37
38 #include "dump_entry.h"
39 #include "termsort.c"           /* this C file is generated */
40 #include <parametrized.h>       /* so is this */
41
42 MODULE_ID("$Id: dump_entry.c,v 1.101 2012/10/27 19:45:17 tom Exp $")
43
44 #define INDENT                  8
45 #define DISCARD(string) string = ABSENT_STRING
46 #define PRINTF (void) printf
47
48 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array))
49
50 typedef struct {
51     char *text;
52     size_t used;
53     size_t size;
54 } DYNBUF;
55
56 static int tversion;            /* terminfo version */
57 static int outform;             /* output format to use */
58 static int sortmode;            /* sort mode to use */
59 static int width = 60;          /* max line width for listings */
60 static int height = 65535;      /* max number of lines for listings */
61 static int column;              /* current column, limited by 'width' */
62 static int oldcol;              /* last value of column before wrap */
63 static bool pretty;             /* true if we format if-then-else strings */
64
65 static char *save_sgr;
66
67 static DYNBUF outbuf;
68 static DYNBUF tmpbuf;
69
70 /* indirection pointers for implementing sort and display modes */
71 static const PredIdx *bool_indirect, *num_indirect, *str_indirect;
72 static NCURSES_CONST char *const *bool_names;
73 static NCURSES_CONST char *const *num_names;
74 static NCURSES_CONST char *const *str_names;
75
76 static const char *separator, *trailer;
77
78 /* cover various ports and variants of terminfo */
79 #define V_ALLCAPS       0       /* all capabilities (SVr4, XSI, ncurses) */
80 #define V_SVR1          1       /* SVR1, Ultrix */
81 #define V_HPUX          2       /* HP/UX */
82 #define V_AIX           3       /* AIX */
83 #define V_BSD           4       /* BSD */
84
85 #if NCURSES_XNAMES
86 #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T'))
87 #else
88 #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T')
89 #endif
90
91 #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && OBSOLETE(n))
92
93 #if NCURSES_XNAMES
94 #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j]))
95 #define NumIndirect(j)  ((j >= NUMCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j]))
96 #define StrIndirect(j)  ((j >= STRCOUNT)  ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j]))
97 #else
98 #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j])
99 #define NumIndirect(j)  ((sortmode == S_NOSORT) ? (j) : num_indirect[j])
100 #define StrIndirect(j)  ((sortmode == S_NOSORT) ? (j) : str_indirect[j])
101 #endif
102
103 static void
104 failed(const char *s)
105 {
106     perror(s);
107     ExitProgram(EXIT_FAILURE);
108 }
109
110 static void
111 strncpy_DYN(DYNBUF * dst, const char *src, size_t need)
112 {
113     size_t want = need + dst->used + 1;
114     if (want > dst->size) {
115         dst->size += (want + 1024);     /* be generous */
116         dst->text = typeRealloc(char, dst->size, dst->text);
117         if (dst->text == 0)
118             failed("strncpy_DYN");
119     }
120     (void) strncpy(dst->text + dst->used, src, need);
121     dst->used += need;
122     dst->text[dst->used] = 0;
123 }
124
125 static void
126 strcpy_DYN(DYNBUF * dst, const char *src)
127 {
128     if (src == 0) {
129         dst->used = 0;
130         strcpy_DYN(dst, "");
131     } else {
132         strncpy_DYN(dst, src, strlen(src));
133     }
134 }
135
136 #if NO_LEAKS
137 static void
138 free_DYN(DYNBUF * p)
139 {
140     if (p->text != 0)
141         free(p->text);
142     p->text = 0;
143     p->size = 0;
144     p->used = 0;
145 }
146
147 void
148 _nc_leaks_dump_entry(void)
149 {
150     free_DYN(&outbuf);
151     free_DYN(&tmpbuf);
152 }
153 #endif
154
155 #define NameTrans(check,result) \
156             if (OkIndex(np->nte_index, check) \
157                 && check[np->nte_index]) \
158                 return (result[np->nte_index])
159
160 NCURSES_CONST char *
161 nametrans(const char *name)
162 /* translate a capability name from termcap to terminfo */
163 {
164     const struct name_table_entry *np;
165
166     if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0)
167         switch (np->nte_type) {
168         case BOOLEAN:
169             NameTrans(bool_from_termcap, boolcodes);
170             break;
171
172         case NUMBER:
173             NameTrans(num_from_termcap, numcodes);
174             break;
175
176         case STRING:
177             NameTrans(str_from_termcap, strcodes);
178             break;
179         }
180
181     return (0);
182 }
183
184 void
185 dump_init(const char *version,
186           int mode,
187           int sort,
188           int twidth,
189           int theight,
190           unsigned traceval,
191           bool formatted)
192 /* set up for entry display */
193 {
194     width = twidth;
195     height = theight;
196     pretty = formatted;
197
198     /* versions */
199     if (version == 0)
200         tversion = V_ALLCAPS;
201     else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1")
202              || !strcmp(version, "Ultrix"))
203         tversion = V_SVR1;
204     else if (!strcmp(version, "HP"))
205         tversion = V_HPUX;
206     else if (!strcmp(version, "AIX"))
207         tversion = V_AIX;
208     else if (!strcmp(version, "BSD"))
209         tversion = V_BSD;
210     else
211         tversion = V_ALLCAPS;
212
213     /* implement display modes */
214     switch (outform = mode) {
215     case F_LITERAL:
216     case F_TERMINFO:
217         bool_names = boolnames;
218         num_names = numnames;
219         str_names = strnames;
220         separator = (twidth > 0 && theight > 1) ? ", " : ",";
221         trailer = "\n\t";
222         break;
223
224     case F_VARIABLE:
225         bool_names = boolfnames;
226         num_names = numfnames;
227         str_names = strfnames;
228         separator = (twidth > 0 && theight > 1) ? ", " : ",";
229         trailer = "\n\t";
230         break;
231
232     case F_TERMCAP:
233     case F_TCONVERR:
234         bool_names = boolcodes;
235         num_names = numcodes;
236         str_names = strcodes;
237         separator = ":";
238         trailer = "\\\n\t:";
239         break;
240     }
241
242     /* implement sort modes */
243     switch (sortmode = sort) {
244     case S_NOSORT:
245         if (traceval)
246             (void) fprintf(stderr,
247                            "%s: sorting by term structure order\n", _nc_progname);
248         break;
249
250     case S_TERMINFO:
251         if (traceval)
252             (void) fprintf(stderr,
253                            "%s: sorting by terminfo name order\n", _nc_progname);
254         bool_indirect = bool_terminfo_sort;
255         num_indirect = num_terminfo_sort;
256         str_indirect = str_terminfo_sort;
257         break;
258
259     case S_VARIABLE:
260         if (traceval)
261             (void) fprintf(stderr,
262                            "%s: sorting by C variable order\n", _nc_progname);
263         bool_indirect = bool_variable_sort;
264         num_indirect = num_variable_sort;
265         str_indirect = str_variable_sort;
266         break;
267
268     case S_TERMCAP:
269         if (traceval)
270             (void) fprintf(stderr,
271                            "%s: sorting by termcap name order\n", _nc_progname);
272         bool_indirect = bool_termcap_sort;
273         num_indirect = num_termcap_sort;
274         str_indirect = str_termcap_sort;
275         break;
276     }
277
278     if (traceval)
279         (void) fprintf(stderr,
280                        "%s: width = %d, tversion = %d, outform = %d\n",
281                        _nc_progname, width, tversion, outform);
282 }
283
284 static TERMTYPE *cur_type;
285
286 static int
287 dump_predicate(PredType type, PredIdx idx)
288 /* predicate function to use for ordinary decompilation */
289 {
290     switch (type) {
291     case BOOLEAN:
292         return (cur_type->Booleans[idx] == FALSE)
293             ? FAIL : cur_type->Booleans[idx];
294
295     case NUMBER:
296         return (cur_type->Numbers[idx] == ABSENT_NUMERIC)
297             ? FAIL : cur_type->Numbers[idx];
298
299     case STRING:
300         return (cur_type->Strings[idx] != ABSENT_STRING)
301             ? (int) TRUE : FAIL;
302     }
303
304     return (FALSE);             /* pacify compiler */
305 }
306
307 static void set_obsolete_termcaps(TERMTYPE *tp);
308
309 /* is this the index of a function key string? */
310 #define FNKEY(i)        (((i)<= 65 && (i)>= 75) || ((i)<= 216 && (i)>= 268))
311
312 /*
313  * If we configure with a different Caps file, the offsets into the arrays
314  * will change.  So we use an address expression.
315  */
316 #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0]))
317 #define NUM_IDX(name)  (PredType) (&(name) - &(CUR Numbers[0]))
318 #define STR_IDX(name)  (PredType) (&(name) - &(CUR Strings[0]))
319
320 static bool
321 version_filter(PredType type, PredIdx idx)
322 /* filter out capabilities we may want to suppress */
323 {
324     switch (tversion) {
325     case V_ALLCAPS:             /* SVr4, XSI Curses */
326         return (TRUE);
327
328     case V_SVR1:                /* System V Release 1, Ultrix */
329         switch (type) {
330         case BOOLEAN:
331             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
332         case NUMBER:
333             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
334         case STRING:
335             return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE);
336         }
337         break;
338
339     case V_HPUX:                /* Hewlett-Packard */
340         switch (type) {
341         case BOOLEAN:
342             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
343         case NUMBER:
344             return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE);
345         case STRING:
346             if (idx <= STR_IDX(prtr_non))
347                 return (TRUE);
348             else if (FNKEY(idx))        /* function keys */
349                 return (TRUE);
350             else if (idx == STR_IDX(plab_norm)
351                      || idx == STR_IDX(label_on)
352                      || idx == STR_IDX(label_off))
353                 return (TRUE);
354             else
355                 return (FALSE);
356         }
357         break;
358
359     case V_AIX:         /* AIX */
360         switch (type) {
361         case BOOLEAN:
362             return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE);
363         case NUMBER:
364             return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE);
365         case STRING:
366             if (idx <= STR_IDX(prtr_non))
367                 return (TRUE);
368             else if (FNKEY(idx))        /* function keys */
369                 return (TRUE);
370             else
371                 return (FALSE);
372         }
373         break;
374
375 #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \
376                           type##_from_termcap[idx])
377
378     case V_BSD:         /* BSD */
379         switch (type) {
380         case BOOLEAN:
381             return is_termcap(bool);
382         case NUMBER:
383             return is_termcap(num);
384         case STRING:
385             return is_termcap(str);
386         }
387         break;
388     }
389
390     return (FALSE);             /* pacify the compiler */
391 }
392
393 static void
394 trim_trailing(void)
395 {
396     while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ')
397         outbuf.text[--outbuf.used] = '\0';
398 }
399
400 static void
401 force_wrap(void)
402 {
403     oldcol = column;
404     trim_trailing();
405     strcpy_DYN(&outbuf, trailer);
406     column = INDENT;
407 }
408
409 static void
410 wrap_concat(const char *src)
411 {
412     size_t need = strlen(src);
413     size_t want = strlen(separator) + need;
414
415     if (column > INDENT
416         && column + (int) want > width) {
417         force_wrap();
418     }
419     strcpy_DYN(&outbuf, src);
420     strcpy_DYN(&outbuf, separator);
421     column += (int) need;
422 }
423
424 #define IGNORE_SEP_TRAIL(first,last,sep_trail) \
425         if ((size_t)(last - first) > sizeof(sep_trail)-1 \
426          && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \
427                 first += sizeof(sep_trail)-2
428
429 /* Returns the nominal length of the buffer assuming it is termcap format,
430  * i.e., the continuation sequence is treated as a single character ":".
431  *
432  * There are several implementations of termcap which read the text into a
433  * fixed-size buffer.  Generally they strip the newlines from the text, but may
434  * not do it until after the buffer is read.  Also, "tc=" resolution may be
435  * expanded in the same buffer.  This function is useful for measuring the size
436  * of the best fixed-buffer implementation; the worst case may be much worse.
437  */
438 #ifdef TEST_TERMCAP_LENGTH
439 static int
440 termcap_length(const char *src)
441 {
442     static const char pattern[] = ":\\\n\t:";
443
444     int len = 0;
445     const char *const t = src + strlen(src);
446
447     while (*src != '\0') {
448         IGNORE_SEP_TRAIL(src, t, pattern);
449         src++;
450         len++;
451     }
452     return len;
453 }
454 #else
455 #define termcap_length(src) strlen(src)
456 #endif
457
458 static void
459 indent_DYN(DYNBUF * buffer, int level)
460 {
461     int n;
462
463     for (n = 0; n < level; n++)
464         strncpy_DYN(buffer, "\t", 1);
465 }
466
467 static bool
468 has_params(const char *src)
469 {
470     bool result = FALSE;
471     int len = (int) strlen(src);
472     int n;
473     bool ifthen = FALSE;
474     bool params = FALSE;
475
476     for (n = 0; n < len - 1; ++n) {
477         if (!strncmp(src + n, "%p", 2)) {
478             params = TRUE;
479         } else if (!strncmp(src + n, "%;", 2)) {
480             ifthen = TRUE;
481             result = params;
482             break;
483         }
484     }
485     if (!ifthen) {
486         result = ((len > 50) && params);
487     }
488     return result;
489 }
490
491 static char *
492 fmt_complex(char *src, int level)
493 {
494     bool percent = FALSE;
495     bool params = has_params(src);
496
497     while (*src != '\0') {
498         switch (*src) {
499         case '\\':
500             percent = FALSE;
501             strncpy_DYN(&tmpbuf, src++, 1);
502             break;
503         case '%':
504             percent = TRUE;
505             break;
506         case '?':               /* "if" */
507         case 't':               /* "then" */
508         case 'e':               /* "else" */
509             if (percent) {
510                 percent = FALSE;
511                 tmpbuf.text[tmpbuf.used - 1] = '\n';
512                 /* treat a "%e" as else-if, on the same level */
513                 if (*src == 'e') {
514                     indent_DYN(&tmpbuf, level);
515                     strncpy_DYN(&tmpbuf, "%", 1);
516                     strncpy_DYN(&tmpbuf, src, 1);
517                     src++;
518                     params = has_params(src);
519                     if (!params && *src != '\0' && *src != '%') {
520                         strncpy_DYN(&tmpbuf, "\n", 1);
521                         indent_DYN(&tmpbuf, level + 1);
522                     }
523                 } else {
524                     indent_DYN(&tmpbuf, level + 1);
525                     strncpy_DYN(&tmpbuf, "%", 1);
526                     strncpy_DYN(&tmpbuf, src, 1);
527                     if (*src++ == '?') {
528                         src = fmt_complex(src, level + 1);
529                         if (*src != '\0' && *src != '%') {
530                             strncpy_DYN(&tmpbuf, "\n", 1);
531                             indent_DYN(&tmpbuf, level + 1);
532                         }
533                     } else if (level == 1) {
534                         _nc_warning("%%%c without %%?", *src);
535                     }
536                 }
537                 continue;
538             }
539             break;
540         case ';':               /* "endif" */
541             if (percent) {
542                 percent = FALSE;
543                 if (level > 1) {
544                     tmpbuf.text[tmpbuf.used - 1] = '\n';
545                     indent_DYN(&tmpbuf, level);
546                     strncpy_DYN(&tmpbuf, "%", 1);
547                     strncpy_DYN(&tmpbuf, src++, 1);
548                     return src;
549                 }
550                 _nc_warning("%%; without %%?");
551             }
552             break;
553         case 'p':
554             if (percent && params) {
555                 tmpbuf.text[tmpbuf.used - 1] = '\n';
556                 indent_DYN(&tmpbuf, level + 1);
557                 strncpy_DYN(&tmpbuf, "%", 1);
558             }
559             params = FALSE;
560             percent = FALSE;
561             break;
562         case ' ':
563             strncpy_DYN(&tmpbuf, "\\s", 2);
564             ++src;
565             continue;
566         default:
567             percent = FALSE;
568             break;
569         }
570         strncpy_DYN(&tmpbuf, src++, 1);
571     }
572     return src;
573 }
574
575 #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
576 #define EXTRA_CAP 20
577
578 int
579 fmt_entry(TERMTYPE *tterm,
580           PredFunc pred,
581           bool content_only,
582           bool suppress_untranslatable,
583           bool infodump,
584           int numbers)
585 {
586     PredIdx i, j;
587     char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP];
588     char *capability;
589     NCURSES_CONST char *name;
590     int predval, len;
591     PredIdx num_bools = 0;
592     PredIdx num_values = 0;
593     PredIdx num_strings = 0;
594     bool outcount = 0;
595
596 #define WRAP_CONCAT     \
597         wrap_concat(buffer); \
598         outcount = TRUE
599
600     len = 12;                   /* terminfo file-header */
601
602     if (pred == 0) {
603         cur_type = tterm;
604         pred = dump_predicate;
605     }
606
607     strcpy_DYN(&outbuf, 0);
608     if (content_only) {
609         column = INDENT;        /* FIXME: workaround to prevent empty lines */
610     } else {
611         strcpy_DYN(&outbuf, tterm->term_names);
612
613         /*
614          * Colon is legal in terminfo descriptions, but not in termcap.
615          */
616         if (!infodump) {
617             char *p = outbuf.text;
618             while (*p) {
619                 if (*p == ':') {
620                     *p = '=';
621                 }
622                 ++p;
623             }
624         }
625         strcpy_DYN(&outbuf, separator);
626         column = (int) outbuf.used;
627         if (height > 1)
628             force_wrap();
629     }
630
631     for_each_boolean(j, tterm) {
632         i = BoolIndirect(j);
633         name = ExtBoolname(tterm, (int) i, bool_names);
634         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
635
636         if (!version_filter(BOOLEAN, i))
637             continue;
638         else if (isObsolete(outform, name))
639             continue;
640
641         predval = pred(BOOLEAN, i);
642         if (predval != FAIL) {
643             _nc_STRCPY(buffer, name, sizeof(buffer));
644             if (predval <= 0)
645                 _nc_STRCAT(buffer, "@", sizeof(buffer));
646             else if (i + 1 > num_bools)
647                 num_bools = i + 1;
648             WRAP_CONCAT;
649         }
650     }
651
652     if (column != INDENT && height > 1)
653         force_wrap();
654
655     for_each_number(j, tterm) {
656         i = NumIndirect(j);
657         name = ExtNumname(tterm, (int) i, num_names);
658         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
659
660         if (!version_filter(NUMBER, i))
661             continue;
662         else if (isObsolete(outform, name))
663             continue;
664
665         predval = pred(NUMBER, i);
666         if (predval != FAIL) {
667             if (tterm->Numbers[i] < 0) {
668                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
669                             "%s@", name);
670             } else {
671                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
672                             "%s#%d", name, tterm->Numbers[i]);
673                 if (i + 1 > num_values)
674                     num_values = i + 1;
675             }
676             WRAP_CONCAT;
677         }
678     }
679
680     if (column != INDENT && height > 1)
681         force_wrap();
682
683     len += (int) (num_bools
684                   + num_values * 2
685                   + strlen(tterm->term_names) + 1);
686     if (len & 1)
687         len++;
688
689 #undef CUR
690 #define CUR tterm->
691     if (outform == F_TERMCAP) {
692         if (termcap_reset != ABSENT_STRING) {
693             if (init_3string != ABSENT_STRING
694                 && !strcmp(init_3string, termcap_reset))
695                 DISCARD(init_3string);
696
697             if (reset_2string != ABSENT_STRING
698                 && !strcmp(reset_2string, termcap_reset))
699                 DISCARD(reset_2string);
700         }
701     }
702
703     for_each_string(j, tterm) {
704         i = StrIndirect(j);
705         name = ExtStrname(tterm, (int) i, str_names);
706         assert(strlen(name) < sizeof(buffer) - EXTRA_CAP);
707
708         capability = tterm->Strings[i];
709
710         if (!version_filter(STRING, i))
711             continue;
712         else if (isObsolete(outform, name))
713             continue;
714
715 #if NCURSES_XNAMES
716         /*
717          * Extended names can be longer than 2 characters, but termcap programs
718          * cannot read those (filter them out).
719          */
720         if (outform == F_TERMCAP && (strlen(name) > 2))
721             continue;
722 #endif
723
724         if (outform == F_TERMCAP) {
725             /*
726              * Some older versions of vi want rmir/smir to be defined
727              * for ich/ich1 to work.  If they're not defined, force
728              * them to be output as defined and empty.
729              */
730             if (PRESENT(insert_character) || PRESENT(parm_ich)) {
731                 if (SAME_CAP(i, enter_insert_mode)
732                     && enter_insert_mode == ABSENT_STRING) {
733                     _nc_STRCPY(buffer, "im=", sizeof(buffer));
734                     WRAP_CONCAT;
735                     continue;
736                 }
737
738                 if (SAME_CAP(i, exit_insert_mode)
739                     && exit_insert_mode == ABSENT_STRING) {
740                     _nc_STRCPY(buffer, "ei=", sizeof(buffer));
741                     WRAP_CONCAT;
742                     continue;
743                 }
744             }
745             /*
746              * termcap applications such as screen will be confused if sgr0
747              * is translated to a string containing rmacs.  Filter that out.
748              */
749             if (PRESENT(exit_attribute_mode)) {
750                 if (SAME_CAP(i, exit_attribute_mode)) {
751                     char *trimmed_sgr0;
752                     char *my_sgr = set_attributes;
753
754                     set_attributes = save_sgr;
755
756                     trimmed_sgr0 = _nc_trim_sgr0(tterm);
757                     if (strcmp(capability, trimmed_sgr0))
758                         capability = trimmed_sgr0;
759
760                     set_attributes = my_sgr;
761                 }
762             }
763         }
764
765         predval = pred(STRING, i);
766         buffer[0] = '\0';
767
768         if (predval != FAIL) {
769             if (capability != ABSENT_STRING
770                 && i + 1 > num_strings)
771                 num_strings = i + 1;
772
773             if (!VALID_STRING(capability)) {
774                 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
775                             "%s@", name);
776                 WRAP_CONCAT;
777             } else if (outform == F_TERMCAP || outform == F_TCONVERR) {
778                 int params = ((i < (int) SIZEOF(parametrized))
779                               ? parametrized[i]
780                               : 0);
781                 char *srccap = _nc_tic_expand(capability, TRUE, numbers);
782                 char *cv = _nc_infotocap(name, srccap, params);
783
784                 if (cv == 0) {
785                     if (outform == F_TCONVERR) {
786                         _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
787                                     "%s=!!! %s WILL NOT CONVERT !!!",
788                                     name, srccap);
789                     } else if (suppress_untranslatable) {
790                         continue;
791                     } else {
792                         char *s = srccap, *d = buffer;
793                         _nc_SPRINTF(d, _nc_SLIMIT(sizeof(buffer)) "..%s=", name);
794                         d += strlen(d);
795                         while ((*d = *s++) != 0) {
796                             if (*d == ':') {
797                                 *d++ = '\\';
798                                 *d = ':';
799                             } else if (*d == '\\') {
800                                 *++d = *s++;
801                             }
802                             d++;
803                         }
804                     }
805                 } else {
806                     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
807                                 "%s=%s", name, cv);
808                 }
809                 len += (int) strlen(capability) + 1;
810                 WRAP_CONCAT;
811             } else {
812                 char *src = _nc_tic_expand(capability,
813                                            outform == F_TERMINFO, numbers);
814
815                 strcpy_DYN(&tmpbuf, 0);
816                 strcpy_DYN(&tmpbuf, name);
817                 strcpy_DYN(&tmpbuf, "=");
818                 if (pretty
819                     && (outform == F_TERMINFO
820                         || outform == F_VARIABLE)) {
821                     fmt_complex(src, 1);
822                 } else {
823                     strcpy_DYN(&tmpbuf, src);
824                 }
825                 len += (int) strlen(capability) + 1;
826                 wrap_concat(tmpbuf.text);
827                 outcount = TRUE;
828             }
829         }
830         /* e.g., trimmed_sgr0 */
831         if (capability != tterm->Strings[i])
832             free(capability);
833     }
834     len += (int) (num_strings * 2);
835
836     /*
837      * This piece of code should be an effective inverse of the functions
838      * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c.
839      * Much more work should be done on this to support dumping termcaps.
840      */
841     if (tversion == V_HPUX) {
842         if (VALID_STRING(memory_lock)) {
843             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
844                         "meml=%s", memory_lock);
845             WRAP_CONCAT;
846         }
847         if (VALID_STRING(memory_unlock)) {
848             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
849                         "memu=%s", memory_unlock);
850             WRAP_CONCAT;
851         }
852     } else if (tversion == V_AIX) {
853         if (VALID_STRING(acs_chars)) {
854             bool box_ok = TRUE;
855             const char *acstrans = "lqkxjmwuvtn";
856             const char *cp;
857             char *tp, *sp, boxchars[11];
858
859             tp = boxchars;
860             for (cp = acstrans; *cp; cp++) {
861                 sp = strchr(acs_chars, *cp);
862                 if (sp)
863                     *tp++ = sp[1];
864                 else {
865                     box_ok = FALSE;
866                     break;
867                 }
868             }
869             tp[0] = '\0';
870
871             if (box_ok) {
872                 _nc_STRCPY(buffer, "box1=", sizeof(buffer));
873                 _nc_STRCAT(buffer,
874                            _nc_tic_expand(boxchars,
875                                           outform == F_TERMINFO, numbers),
876                            sizeof(buffer));
877                 WRAP_CONCAT;
878             }
879         }
880     }
881
882     /*
883      * kludge: trim off trailer to avoid an extra blank line
884      * in infocmp -u output when there are no string differences
885      */
886     if (outcount) {
887         bool trimmed = FALSE;
888         j = (PredIdx) outbuf.used;
889         if (j >= 2
890             && outbuf.text[j - 1] == '\t'
891             && outbuf.text[j - 2] == '\n') {
892             outbuf.used -= 2;
893             trimmed = TRUE;
894         } else if (j >= 4
895                    && outbuf.text[j - 1] == ':'
896                    && outbuf.text[j - 2] == '\t'
897                    && outbuf.text[j - 3] == '\n'
898                    && outbuf.text[j - 4] == '\\') {
899             outbuf.used -= 4;
900             trimmed = TRUE;
901         }
902         if (trimmed) {
903             outbuf.text[outbuf.used] = '\0';
904             column = oldcol;
905             strcpy_DYN(&outbuf, " ");
906         }
907     }
908 #if 0
909     fprintf(stderr, "num_bools = %d\n", num_bools);
910     fprintf(stderr, "num_values = %d\n", num_values);
911     fprintf(stderr, "num_strings = %d\n", num_strings);
912     fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n",
913             tterm->term_names, len, outbuf.used, outbuf.text);
914 #endif
915     /*
916      * Here's where we use infodump to trigger a more stringent length check
917      * for termcap-translation purposes.
918      * Return the length of the raw entry, without tc= expansions,
919      * It gives an idea of which entries are deadly to even *scan past*,
920      * as opposed to *use*.
921      */
922     return (infodump ? len : (int) termcap_length(outbuf.text));
923 }
924
925 static bool
926 kill_string(TERMTYPE *tterm, char *cap)
927 {
928     unsigned n;
929     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
930         if (cap == tterm->Strings[n]) {
931             tterm->Strings[n] = ABSENT_STRING;
932             return TRUE;
933         }
934     }
935     return FALSE;
936 }
937
938 static char *
939 find_string(TERMTYPE *tterm, char *name)
940 {
941     PredIdx n;
942     for (n = 0; n < NUM_STRINGS(tterm); ++n) {
943         if (version_filter(STRING, n)
944             && !strcmp(name, strnames[n])) {
945             char *cap = tterm->Strings[n];
946             if (VALID_STRING(cap)) {
947                 return cap;
948             }
949             break;
950         }
951     }
952     return ABSENT_STRING;
953 }
954
955 /*
956  * This is used to remove function-key labels from a termcap entry to
957  * make it smaller.
958  */
959 static int
960 kill_labels(TERMTYPE *tterm, int target)
961 {
962     int n;
963     int result = 0;
964     char *cap;
965     char name[10];
966
967     for (n = 0; n <= 10; ++n) {
968         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n);
969         if ((cap = find_string(tterm, name)) != ABSENT_STRING
970             && kill_string(tterm, cap)) {
971             target -= (int) (strlen(cap) + 5);
972             ++result;
973             if (target < 0)
974                 break;
975         }
976     }
977     return result;
978 }
979
980 /*
981  * This is used to remove function-key definitions from a termcap entry to
982  * make it smaller.
983  */
984 static int
985 kill_fkeys(TERMTYPE *tterm, int target)
986 {
987     int n;
988     int result = 0;
989     char *cap;
990     char name[10];
991
992     for (n = 60; n >= 0; --n) {
993         _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n);
994         if ((cap = find_string(tterm, name)) != ABSENT_STRING
995             && kill_string(tterm, cap)) {
996             target -= (int) (strlen(cap) + 5);
997             ++result;
998             if (target < 0)
999                 break;
1000         }
1001     }
1002     return result;
1003 }
1004
1005 /*
1006  * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100.
1007  * Also, since this is for termcap, we only care about the line-drawing map.
1008  */
1009 #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0)
1010
1011 static bool
1012 one_one_mapping(const char *mapping)
1013 {
1014     bool result = TRUE;
1015
1016     if (mapping != ABSENT_STRING) {
1017         int n = 0;
1018         while (mapping[n] != '\0') {
1019             if (isLine(mapping[n]) &&
1020                 mapping[n] != mapping[n + 1]) {
1021                 result = FALSE;
1022                 break;
1023             }
1024             n += 2;
1025         }
1026     }
1027     return result;
1028 }
1029
1030 #define FMT_ENTRY() \
1031                 fmt_entry(tterm, pred, \
1032                         0, \
1033                         suppress_untranslatable, \
1034                         infodump, numbers)
1035
1036 #define SHOW_WHY PRINTF
1037
1038 static bool
1039 purged_acs(TERMTYPE *tterm)
1040 {
1041     bool result = FALSE;
1042
1043     if (VALID_STRING(acs_chars)) {
1044         if (!one_one_mapping(acs_chars)) {
1045             enter_alt_charset_mode = ABSENT_STRING;
1046             exit_alt_charset_mode = ABSENT_STRING;
1047             SHOW_WHY("# (rmacs/smacs removed for consistency)\n");
1048         }
1049         result = TRUE;
1050     }
1051     return result;
1052 }
1053
1054 /*
1055  * Dump a single entry.
1056  */
1057 void
1058 dump_entry(TERMTYPE *tterm,
1059            bool suppress_untranslatable,
1060            bool limited,
1061            int numbers,
1062            PredFunc pred)
1063 {
1064     TERMTYPE save_tterm;
1065     int len, critlen;
1066     const char *legend;
1067     bool infodump;
1068
1069     if (outform == F_TERMCAP || outform == F_TCONVERR) {
1070         critlen = MAX_TERMCAP_LENGTH;
1071         legend = "older termcap";
1072         infodump = FALSE;
1073         set_obsolete_termcaps(tterm);
1074     } else {
1075         critlen = MAX_TERMINFO_LENGTH;
1076         legend = "terminfo";
1077         infodump = TRUE;
1078     }
1079
1080     save_sgr = set_attributes;
1081
1082     if ((FMT_ENTRY() > critlen)
1083         && limited) {
1084
1085         save_tterm = *tterm;
1086         if (!suppress_untranslatable) {
1087             SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n",
1088                      critlen);
1089             suppress_untranslatable = TRUE;
1090         }
1091         if (FMT_ENTRY() > critlen) {
1092             /*
1093              * We pick on sgr because it's a nice long string capability that
1094              * is really just an optimization hack.  Another good candidate is
1095              * acsc since it is both long and unused by BSD termcap.
1096              */
1097             bool changed = FALSE;
1098
1099 #if NCURSES_XNAMES
1100             /*
1101              * Extended names are most likely function-key definitions.  Drop
1102              * those first.
1103              */
1104             unsigned n;
1105             for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) {
1106                 const char *name = ExtStrname(tterm, (int) n, strnames);
1107
1108                 if (VALID_STRING(tterm->Strings[n])) {
1109                     set_attributes = ABSENT_STRING;
1110                     /* we remove long names anyway - only report the short */
1111                     if (strlen(name) <= 2) {
1112                         SHOW_WHY("# (%s removed to fit entry within %d bytes)\n",
1113                                  name,
1114                                  critlen);
1115                     }
1116                     changed = TRUE;
1117                     if (FMT_ENTRY() <= critlen)
1118                         break;
1119                 }
1120             }
1121 #endif
1122             if (VALID_STRING(set_attributes)) {
1123                 set_attributes = ABSENT_STRING;
1124                 SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n",
1125                          critlen);
1126                 changed = TRUE;
1127             }
1128             if (!changed || (FMT_ENTRY() > critlen)) {
1129                 if (purged_acs(tterm)) {
1130                     acs_chars = ABSENT_STRING;
1131                     SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n",
1132                              critlen);
1133                     changed = TRUE;
1134                 }
1135             }
1136             if (!changed || (FMT_ENTRY() > critlen)) {
1137                 int oldversion = tversion;
1138
1139                 tversion = V_BSD;
1140                 SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n",
1141                          critlen);
1142
1143                 len = FMT_ENTRY();
1144                 if (len > critlen
1145                     && kill_labels(tterm, len - critlen)) {
1146                     SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n",
1147                              critlen);
1148                     len = FMT_ENTRY();
1149                 }
1150                 if (len > critlen
1151                     && kill_fkeys(tterm, len - critlen)) {
1152                     SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n",
1153                              critlen);
1154                     len = FMT_ENTRY();
1155                 }
1156                 if (len > critlen) {
1157                     (void) fprintf(stderr,
1158                                    "warning: %s entry is %d bytes long\n",
1159                                    _nc_first_name(tterm->term_names),
1160                                    len);
1161                     SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n",
1162                              len, legend);
1163                 }
1164                 tversion = oldversion;
1165             }
1166             set_attributes = save_sgr;
1167             *tterm = save_tterm;
1168         }
1169     } else if (!version_filter(STRING, STR_IDX(acs_chars))) {
1170         save_tterm = *tterm;
1171         if (purged_acs(tterm)) {
1172             (void) FMT_ENTRY();
1173         }
1174         *tterm = save_tterm;
1175     }
1176 }
1177
1178 void
1179 dump_uses(const char *name, bool infodump)
1180 /* dump "use=" clauses in the appropriate format */
1181 {
1182     char buffer[MAX_TERMINFO_LENGTH];
1183
1184     if (outform == F_TERMCAP || outform == F_TCONVERR)
1185         trim_trailing();
1186     _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
1187                 "%s%s", infodump ? "use=" : "tc=", name);
1188     wrap_concat(buffer);
1189 }
1190
1191 int
1192 show_entry(void)
1193 {
1194     /*
1195      * Trim any remaining whitespace.
1196      */
1197     if (outbuf.used != 0) {
1198         bool infodump = (outform != F_TERMCAP && outform != F_TCONVERR);
1199         char delim = (char) (infodump ? ',' : ':');
1200         int j;
1201
1202         for (j = (int) outbuf.used - 1; j > 0; --j) {
1203             char ch = outbuf.text[j];
1204             if (ch == '\n') {
1205                 ;
1206             } else if (isspace(UChar(ch))) {
1207                 outbuf.used = (size_t) j;
1208             } else if (!infodump && ch == '\\') {
1209                 outbuf.used = (size_t) j;
1210             } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) {
1211                 outbuf.used = (size_t) (j + 1);
1212             } else {
1213                 break;
1214             }
1215         }
1216         outbuf.text[outbuf.used] = '\0';
1217     }
1218     (void) fputs(outbuf.text, stdout);
1219     putchar('\n');
1220     return (int) outbuf.used;
1221 }
1222
1223 void
1224 compare_entry(PredHook hook,
1225               TERMTYPE *tp GCC_UNUSED,
1226               bool quiet)
1227 /* compare two entries */
1228 {
1229     PredIdx i, j;
1230     NCURSES_CONST char *name;
1231
1232     if (!quiet)
1233         fputs("    comparing booleans.\n", stdout);
1234     for_each_boolean(j, tp) {
1235         i = BoolIndirect(j);
1236         name = ExtBoolname(tp, (int) i, bool_names);
1237
1238         if (isObsolete(outform, name))
1239             continue;
1240
1241         (*hook) (CMP_BOOLEAN, i, name);
1242     }
1243
1244     if (!quiet)
1245         fputs("    comparing numbers.\n", stdout);
1246     for_each_number(j, tp) {
1247         i = NumIndirect(j);
1248         name = ExtNumname(tp, (int) i, num_names);
1249
1250         if (isObsolete(outform, name))
1251             continue;
1252
1253         (*hook) (CMP_NUMBER, i, name);
1254     }
1255
1256     if (!quiet)
1257         fputs("    comparing strings.\n", stdout);
1258     for_each_string(j, tp) {
1259         i = StrIndirect(j);
1260         name = ExtStrname(tp, (int) i, str_names);
1261
1262         if (isObsolete(outform, name))
1263             continue;
1264
1265         (*hook) (CMP_STRING, i, name);
1266     }
1267
1268     /* (void) fputs("    comparing use entries.\n", stdout); */
1269     (*hook) (CMP_USE, 0, "use");
1270
1271 }
1272
1273 #define NOTSET(s)       ((s) == 0)
1274
1275 /*
1276  * This bit of legerdemain turns all the terminfo variable names into
1277  * references to locations in the arrays Booleans, Numbers, and Strings ---
1278  * precisely what's needed.
1279  */
1280 #undef CUR
1281 #define CUR tp->
1282
1283 static void
1284 set_obsolete_termcaps(TERMTYPE *tp)
1285 {
1286 #include "capdefaults.c"
1287 }
1288
1289 /*
1290  * Convert an alternate-character-set string to canonical form: sorted and
1291  * unique.
1292  */
1293 void
1294 repair_acsc(TERMTYPE *tp)
1295 {
1296     if (VALID_STRING(acs_chars)) {
1297         size_t n, m;
1298         char mapped[256];
1299         char extra = 0;
1300         unsigned source;
1301         unsigned target;
1302         bool fix_needed = FALSE;
1303
1304         for (n = 0, source = 0; acs_chars[n] != 0; n++) {
1305             target = UChar(acs_chars[n]);
1306             if (source >= target) {
1307                 fix_needed = TRUE;
1308                 break;
1309             }
1310             source = target;
1311             if (acs_chars[n + 1])
1312                 n++;
1313         }
1314         if (fix_needed) {
1315             memset(mapped, 0, sizeof(mapped));
1316             for (n = 0; acs_chars[n] != 0; n++) {
1317                 source = UChar(acs_chars[n]);
1318                 if ((target = (unsigned char) acs_chars[n + 1]) != 0) {
1319                     mapped[source] = (char) target;
1320                     n++;
1321                 } else {
1322                     extra = (char) source;
1323                 }
1324             }
1325             for (n = m = 0; n < sizeof(mapped); n++) {
1326                 if (mapped[n]) {
1327                     acs_chars[m++] = (char) n;
1328                     acs_chars[m++] = mapped[n];
1329                 }
1330             }
1331             if (extra)
1332                 acs_chars[m++] = extra;         /* garbage in, garbage out */
1333             acs_chars[m] = 0;
1334         }
1335     }
1336 }