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