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