ncurses 5.9 - patch 20150404
[ncurses.git] / progs / tic.c
index 4d265eb1fed5a1ee55ce48ab6ba3338c232c30a8..a9a4edc531e0c508faac4c691fb02eaa6655b76d 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * Copyright (c) 1998-2012,2013 Free Software Foundation, Inc.              *
+ * Copyright (c) 1998-2014,2015 Free Software Foundation, Inc.              *
  *                                                                          *
  * Permission is hereby granted, free of charge, to any person obtaining a  *
  * copy of this software and associated documentation files (the            *
 #include <sys/stat.h>
 
 #include <dump_entry.h>
+#include <tparm_type.h>
 #include <hashed_db.h>
+#include <parametrized.h>
 #include <transform.h>
 
-MODULE_ID("$Id: tic.c,v 1.186 2013/06/08 16:50:47 tom Exp $")
+MODULE_ID("$Id: tic.c,v 1.209 2015/04/04 14:53:41 tom Exp $")
 
 #define STDIN_NAME "<stdin>"
 
@@ -136,50 +138,48 @@ failed(const char *msg)
 static void
 usage(void)
 {
-    static const char *const tbl[] =
+#define DATA(s) s "\n"
+    static const char options_string[] =
     {
-       "Options:",
-       "  -1         format translation output one capability per line",
+       DATA("Options:")
+       DATA("  -1         format translation output one capability per line")
 #if NCURSES_XNAMES
-       "  -a         retain commented-out capabilities (sets -x also)",
+       DATA("  -a         retain commented-out capabilities (sets -x also)")
 #endif
-       "  -K         translate entries to termcap source form with BSD syntax",
-       "  -C         translate entries to termcap source form",
-       "  -D         print list of tic's database locations (first must be writable)",
-       "  -c         check only, validate input without compiling or translating",
-       "  -e<names>  translate/compile only entries named by comma-separated list",
-       "  -f         format complex strings for readability",
-       "  -G         format %{number} to %'char'",
-       "  -g         format %'char' to %{number}",
-       "  -I         translate entries to terminfo source form",
-       "  -L         translate entries to full terminfo source form",
-       "  -N         disable smart defaults for source translation",
-       "  -o<dir>    set output directory for compiled entry writes",
-       "  -R<name>   restrict translation to given terminfo/termcap version",
-       "  -r         force resolution of all use entries in source translation",
-       "  -s         print summary statistics",
-       "  -T         remove size-restrictions on compiled description",
+       DATA("  -C         translate entries to termcap source form")
+       DATA("  -D         print list of tic's database locations (first must be writable)")
+       DATA("  -c         check only, validate input without compiling or translating")
+       DATA("  -e<names>  translate/compile only entries named by comma-separated list")
+       DATA("  -f         format complex strings for readability")
+       DATA("  -G         format %{number} to %'char'")
+       DATA("  -g         format %'char' to %{number}")
+       DATA("  -I         translate entries to terminfo source form")
+       DATA("  -K         translate entries to termcap source form with BSD syntax")
+       DATA("  -L         translate entries to full terminfo source form")
+       DATA("  -N         disable smart defaults for source translation")
+       DATA("  -o<dir>    set output directory for compiled entry writes")
+       DATA("  -R<name>   restrict translation to given terminfo/termcap version")
+       DATA("  -r         force resolution of all use entries in source translation")
+       DATA("  -s         print summary statistics")
+       DATA("  -T         remove size-restrictions on compiled description")
 #if NCURSES_XNAMES
-       "  -t         suppress commented-out capabilities",
+       DATA("  -t         suppress commented-out capabilities")
 #endif
-       "  -U         suppress post-processing of entries",
-       "  -V         print version",
-       "  -v[n]      set verbosity level",
-       "  -w[n]      set format width for translation output",
+       DATA("  -U         suppress post-processing of entries")
+       DATA("  -V         print version")
+       DATA("  -v[n]      set verbosity level")
+       DATA("  -w[n]      set format width for translation output")
 #if NCURSES_XNAMES
-       "  -x         treat unknown capabilities as user-defined",
+       DATA("  -x         treat unknown capabilities as user-defined")
 #endif
-       "",
-       "Parameters:",
-       "  <file>     file to translate or compile"
+       DATA("")
+       DATA("Parameters:")
+       DATA("  <file>     file to translate or compile")
     };
-    size_t j;
+#undef DATA
 
     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
-    for (j = 0; j < SIZEOF(tbl); j++) {
-       fputs(tbl[j], stderr);
-       putc('\n', stderr);
-    }
+    fputs(options_string, stderr);
     ExitProgram(EXIT_FAILURE);
 }
 
@@ -380,11 +380,11 @@ open_tempfile(char *filename)
     _nc_STRCPY(filename, "/tmp/XXXXXX", PATH_MAX);
 #if HAVE_MKSTEMP
     {
-       int oldmask = umask(077);
+       int oldmask = (int) umask(077);
        int fd = mkstemp(filename);
        if (fd >= 0)
            result = fdopen(fd, "w");
-       umask(oldmask);
+       umask((mode_t) oldmask);
     }
 #else
     if (tmpnam(filename) != 0)
@@ -450,7 +450,7 @@ open_input(const char *filename, char *alt_file)
        fprintf(stderr, "%s: %s %s\n", _nc_progname, filename, strerror(errno));
        ExitProgram(EXIT_FAILURE);
     } else if ((mode = (sb.st_mode & S_IFMT)) == S_IFDIR
-              || (mode != S_IFREG && mode != S_IFCHR)) {
+              || (mode != S_IFREG && mode != S_IFCHR && mode != S_IFIFO)) {
        fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
        ExitProgram(EXIT_FAILURE);
     } else {
@@ -939,7 +939,7 @@ main(int argc, char *argv[])
     }
 
     /* length check */
-    if (check_only && (capdump || infodump)) {
+    if (check_only && limited && (capdump || infodump)) {
        for_entry_list(qp) {
            if (matches(namelst, qp->tterm.term_names)) {
                int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
@@ -1114,6 +1114,17 @@ check_colors(TERMTYPE *tp)
        if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
            _nc_warning("expected either op/oc string for resetting colors");
     }
+    if (can_change) {
+       if (!VALID_STRING(initialize_pair) &&
+           !VALID_STRING(initialize_color)) {
+           _nc_warning("expected initc or initp because ccc is given");
+       }
+    } else {
+       if (VALID_STRING(initialize_pair) ||
+           VALID_STRING(initialize_color)) {
+           _nc_warning("expected ccc because initc is given");
+       }
+    }
 }
 
 static char
@@ -1140,7 +1151,7 @@ keypad_index(const char *string)
     long result = -1;
 
     if ((ch = keypad_final(string)) != '\0') {
-       test = strchr(list, ch);
+       test = (strchr) (list, ch);
        if (test != 0)
            result = (long) (test - list);
     }
@@ -1224,6 +1235,19 @@ check_ansi_cursor(char *list[4])
 }
 
 #define EXPECTED(name) if (!PRESENT(name)) _nc_warning("expected " #name)
+#define UNEXPECTED(name) if (PRESENT(name)) _nc_warning("unexpected " #name ", for %s", why)
+
+static void
+check_noaddress(TERMTYPE *tp, const char *why)
+{
+    UNEXPECTED(column_address);
+    UNEXPECTED(cursor_address);
+    UNEXPECTED(cursor_home);
+    UNEXPECTED(cursor_mem_address);
+    UNEXPECTED(cursor_to_ll);
+    UNEXPECTED(row_address);
+    UNEXPECTED(row_address);
+}
 
 static void
 check_cursor(TERMTYPE *tp)
@@ -1231,6 +1255,43 @@ check_cursor(TERMTYPE *tp)
     int count;
     char *list[4];
 
+    if (hard_copy) {
+       check_noaddress(tp, "hard_copy");
+    } else if (generic_type) {
+       check_noaddress(tp, "generic_type");
+    } else if (strchr(tp->term_names, '+') == 0) {
+       int y = 0;
+       int x = 0;
+       if (PRESENT(column_address))
+           ++y;
+       if (PRESENT(cursor_address))
+           y = x = 10;
+       if (PRESENT(cursor_home))
+           ++y, ++x;
+       if (PRESENT(cursor_mem_address))
+           y = x = 10;
+       if (PRESENT(cursor_to_ll))
+           ++y, ++x;
+       if (PRESENT(row_address))
+           ++x;
+       if (PRESENT(cursor_down))
+           ++y;
+       if (PRESENT(cursor_up))
+           ++y;
+       if (PRESENT(cursor_left))
+           ++x;
+       if (PRESENT(cursor_right))
+           ++x;
+       if (x < 2 && y < 2) {
+           _nc_warning("terminal lacks cursor addressing");
+       } else {
+           if (x < 2)
+               _nc_warning("terminal lacks cursor column-addressing");
+           if (y < 2)
+               _nc_warning("terminal lacks cursor row-addressing");
+       }
+    }
+
     /* it is rare to have an insert-line feature without a matching delete */
     ANDMISSING(parm_insert_line, insert_line);
     ANDMISSING(parm_delete_line, delete_line);
@@ -1412,7 +1473,7 @@ check_keypad(TERMTYPE *tp)
 
     /*
      * These warnings are useful for consistency checks - it is possible that
-     * there are real terminals with mismatches in these 
+     * there are real terminals with mismatches in these
      */
     ANDMISSING(key_ic, key_dc);
 }
@@ -1517,78 +1578,81 @@ check_screen(TERMTYPE *tp)
 static int
 expected_params(const char *name)
 {
+#define DATA(name,count) { { name }, count }
     /* *INDENT-OFF* */
     static const struct {
-       const char *name;
+       const char name[9];
        int count;
     } table[] = {
-       { "S0",                 1 },    /* 'screen' extension */
-       { "birep",              2 },
-       { "chr",                1 },
-       { "colornm",            1 },
-       { "cpi",                1 },
-       { "csnm",               1 },
-       { "csr",                2 },
-       { "cub",                1 },
-       { "cud",                1 },
-       { "cuf",                1 },
-       { "cup",                2 },
-       { "cuu",                1 },
-       { "cvr",                1 },
-       { "cwin",               5 },
-       { "dch",                1 },
-       { "defc",               3 },
-       { "dial",               1 },
-       { "dispc",              1 },
-       { "dl",                 1 },
-       { "ech",                1 },
-       { "getm",               1 },
-       { "hpa",                1 },
-       { "ich",                1 },
-       { "il",                 1 },
-       { "indn",               1 },
-       { "initc",              4 },
-       { "initp",              7 },
-       { "lpi",                1 },
-       { "mc5p",               1 },
-       { "mrcup",              2 },
-       { "mvpa",               1 },
-       { "pfkey",              2 },
-       { "pfloc",              2 },
-       { "pfx",                2 },
-       { "pfxl",               3 },
-       { "pln",                2 },
-       { "qdial",              1 },
-       { "rcsd",               1 },
-       { "rep",                2 },
-       { "rin",                1 },
-       { "sclk",               3 },
-       { "scp",                1 },
-       { "scs",                1 },
-       { "scsd",               2 },
-       { "setab",              1 },
-       { "setaf",              1 },
-       { "setb",               1 },
-       { "setcolor",           1 },
-       { "setf",               1 },
-       { "sgr",                9 },
-       { "sgr1",               6 },
-       { "slength",            1 },
-       { "slines",             1 },
-       { "smgbp",              1 },    /* 2 if smgtp is not given */
-       { "smglp",              1 },
-       { "smglr",              2 },
-       { "smgrp",              1 },
-       { "smgtb",              2 },
-       { "smgtp",              1 },
-       { "tsl",                1 },
-       { "u6",                 -1 },
-       { "vpa",                1 },
-       { "wind",               4 },
-       { "wingo",              1 },
+       DATA( "S0",             1 ),    /* 'screen' extension */
+       DATA( "birep",          2 ),
+       DATA( "chr",            1 ),
+       DATA( "colornm",        1 ),
+       DATA( "cpi",            1 ),
+       DATA( "csnm",           1 ),
+       DATA( "csr",            2 ),
+       DATA( "cub",            1 ),
+       DATA( "cud",            1 ),
+       DATA( "cuf",            1 ),
+       DATA( "cup",            2 ),
+       DATA( "cuu",            1 ),
+       DATA( "cvr",            1 ),
+       DATA( "cwin",           5 ),
+       DATA( "dch",            1 ),
+       DATA( "defc",           3 ),
+       DATA( "dial",           1 ),
+       DATA( "dispc",          1 ),
+       DATA( "dl",             1 ),
+       DATA( "ech",            1 ),
+       DATA( "getm",           1 ),
+       DATA( "hpa",            1 ),
+       DATA( "ich",            1 ),
+       DATA( "il",             1 ),
+       DATA( "indn",           1 ),
+       DATA( "initc",          4 ),
+       DATA( "initp",          7 ),
+       DATA( "lpi",            1 ),
+       DATA( "mc5p",           1 ),
+       DATA( "mrcup",          2 ),
+       DATA( "mvpa",           1 ),
+       DATA( "pfkey",          2 ),
+       DATA( "pfloc",          2 ),
+       DATA( "pfx",            2 ),
+       DATA( "pfxl",           3 ),
+       DATA( "pln",            2 ),
+       DATA( "qdial",          1 ),
+       DATA( "rcsd",           1 ),
+       DATA( "rep",            2 ),
+       DATA( "rin",            1 ),
+       DATA( "sclk",           3 ),
+       DATA( "scp",            1 ),
+       DATA( "scs",            1 ),
+       DATA( "scsd",           2 ),
+       DATA( "setab",          1 ),
+       DATA( "setaf",          1 ),
+       DATA( "setb",           1 ),
+       DATA( "setcolor",       1 ),
+       DATA( "setf",           1 ),
+       DATA( "sgr",            9 ),
+       DATA( "sgr1",           6 ),
+       DATA( "slength",        1 ),
+       DATA( "slines",         1 ),
+       DATA( "smgbp",          1 ),    /* 2 if smgtp is not given */
+       DATA( "smglp",          1 ),
+       DATA( "smglr",          2 ),
+       DATA( "smgrp",          1 ),
+       DATA( "smgtb",          2 ),
+       DATA( "smgtp",          1 ),
+       DATA( "tsl",            1 ),
+       DATA( "u6",             -1 ),
+       DATA( "vpa",            1 ),
+       DATA( "wind",           4 ),
+       DATA( "wingo",          1 ),
     };
     /* *INDENT-ON* */
 
+#undef DATA
+
     unsigned n;
     int result = 0;            /* function-keys, etc., use none */
 
@@ -1613,7 +1677,7 @@ check_params(TERMTYPE *tp, const char *name, char *value)
     int expected = expected_params(name);
     int actual = 0;
     int n;
-    bool params[10];
+    bool params[NUM_PARM];
     char *s = value;
 
 #ifdef set_top_margin_parm
@@ -1622,7 +1686,7 @@ check_params(TERMTYPE *tp, const char *name, char *value)
        expected = 2;
 #endif
 
-    for (n = 0; n < 10; n++)
+    for (n = 0; n < NUM_PARM; n++)
        params[n] = FALSE;
 
     while (*s != 0) {
@@ -1660,6 +1724,228 @@ check_params(TERMTYPE *tp, const char *name, char *value)
     }
 }
 
+static char *
+check_1_infotocap(const char *name, NCURSES_CONST char *value, int count)
+{
+    int k;
+    int ignored;
+    long numbers[1 + NUM_PARM];
+    char *strings[1 + NUM_PARM];
+    char *p_is_s[NUM_PARM];
+    char *result;
+    char blob[NUM_PARM * 10];
+    char *next = blob;
+
+    *next++ = '\0';
+    for (k = 1; k <= NUM_PARM; k++) {
+       numbers[k] = count;
+       sprintf(next, "XYZ%d", count);
+       strings[k] = next;
+       next += strlen(next) + 1;
+    }
+
+    switch (tparm_type(name)) {
+    case Num_Str:
+       result = TPARM_2(value, numbers[1], strings[2]);
+       break;
+    case Num_Str_Str:
+       result = TPARM_3(value, numbers[1], strings[2], strings[3]);
+       break;
+    case Numbers:
+    default:
+       (void) _nc_tparm_analyze(value, p_is_s, &ignored);
+#define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n])
+       result = TPARM_9(value,
+                        myParam(1),
+                        myParam(2),
+                        myParam(3),
+                        myParam(4),
+                        myParam(5),
+                        myParam(6),
+                        myParam(7),
+                        myParam(8),
+                        myParam(9));
+       break;
+    }
+    return result;
+}
+
+#define IsDelay(ch) ((ch) == '.' || isdigit(UChar(ch)))
+
+static const char *
+parse_delay_value(const char *src, double *delays, int *always)
+{
+    int star = 0;
+
+    *delays = 0.0;
+    if (always)
+       *always = 0;
+
+    while (isdigit(UChar(*src))) {
+       (*delays) = (*delays) * 10 + (*src++ - '0');
+    }
+    if (*src == '.') {
+       int gotdot = 1;
+
+       ++src;
+       while (isdigit(UChar(*src))) {
+           gotdot *= 10;
+           (*delays) += (*src++ - '0') / gotdot;
+       }
+    }
+    while (*src == '*' || *src == '/') {
+       if (always == 0 && *src == '/')
+           break;
+       if (*src++ == '*') {
+           star = 1;
+       } else {
+           *always = 1;
+       }
+    }
+    if (star)
+       *delays = -(*delays);
+    return src;
+}
+
+static const char *
+parse_ti_delay(const char *ti, double *delays)
+{
+    *delays = 0.0;
+    while (*ti != '\0') {
+       if (*ti == '\\') {
+           ++ti;
+       }
+       if (ti[0] == '$'
+           && ti[1] == '<'
+           && IsDelay(UChar(ti[2]))) {
+           int ignored;
+           const char *last = parse_delay_value(ti + 2, delays, &ignored);
+           if (*last == '>') {
+               ti = last;
+           }
+       } else {
+           ++ti;
+       }
+    }
+    return ti;
+}
+
+static const char *
+parse_tc_delay(const char *tc, double *delays)
+{
+    return parse_delay_value(tc, delays, (int *) 0);
+}
+
+/*
+ * Compare terminfo- and termcap-strings, factoring out delays.
+ */
+static bool
+same_ti_tc(const char *ti, const char *tc, bool * embedded)
+{
+    bool same = TRUE;
+    double ti_delay = 0.0;
+    double tc_delay = 0.0;
+    const char *ti_last;
+
+    *embedded = FALSE;
+    ti_last = parse_ti_delay(ti, &ti_delay);
+    tc = parse_tc_delay(tc, &tc_delay);
+
+    while ((ti < ti_last) && *tc) {
+       if (*ti == '\\' && ispunct(UChar(ti[1]))) {
+           ++ti;
+           if ((*ti == '^') && !strncmp(tc, "\\136", 4)) {
+               ti += 1;
+               tc += 4;
+               continue;
+           }
+       } else if (ti[0] == '$' && ti[1] == '<') {
+           double no_delay;
+           const char *ss = parse_ti_delay(ti, &no_delay);
+           if (ss != ti) {
+               *embedded = TRUE;
+               ti = ss;
+               continue;
+           }
+       }
+       if (*tc == '\\' && ispunct(UChar(tc[1]))) {
+           ++tc;
+       }
+       if (*ti++ != *tc++) {
+           same = FALSE;
+           break;
+       }
+    }
+
+    if (*embedded) {
+       if (same) {
+           same = FALSE;
+       } else {
+           *embedded = FALSE;  /* report only one problem */
+       }
+    }
+
+    return same;
+}
+
+/*
+ * Check terminfo to termcap translation.
+ */
+static void
+check_infotocap(TERMTYPE *tp, int i, const char *value)
+{
+    const char *name = ExtStrname(tp, i, strnames);
+    int params = (((i < (int) SIZEOF(parametrized)) &&
+                  (i < STRCOUNT))
+                 ? parametrized[i]
+                 : ((*value == 'k')
+                    ? 0
+                    : has_params(value)));
+    int to_char = 0;
+    char *ti_value;
+    char *tc_value;
+    bool embedded;
+
+    if ((ti_value = _nc_tic_expand(value, TRUE, to_char)) == ABSENT_STRING) {
+       _nc_warning("tic-expansion of %s failed", name);
+    } else if ((tc_value = _nc_infotocap(name, ti_value, params)) == ABSENT_STRING) {
+       _nc_warning("tic-conversion of %s failed", name);
+    } else if (params > 0) {
+       int limit = 5;
+       int count;
+       bool first = TRUE;
+
+       if (!strcmp(name, "setf")
+           || !strcmp(name, "setb")
+           || !strcmp(name, "setaf")
+           || !strcmp(name, "setab")) {
+           limit = max_colors;
+       }
+       for (count = 0; count < limit; ++count) {
+           char *ti_check = check_1_infotocap(name, ti_value, count);
+           char *tc_check = check_1_infotocap(name, tc_value, count);
+
+           if (strcmp(ti_check, tc_check)) {
+               if (first) {
+                   fprintf(stderr, "check_infotocap(%s)\n", name);
+                   fprintf(stderr, "...ti '%s'\n", ti_value);
+                   fprintf(stderr, "...tc '%s'\n", tc_value);
+                   first = FALSE;
+               }
+               _nc_warning("tparm-conversion of %s(%d) differs between\n\tterminfo %s\n\ttermcap  %s",
+                           name, count, ti_check, tc_check);
+           }
+       }
+    } else if (params == 0 && !same_ti_tc(ti_value, tc_value, &embedded)) {
+       if (embedded) {
+           _nc_warning("termcap equivalent of %s cannot use embedded delay", name);
+       } else {
+           _nc_warning("tic-conversion of %s changed value\n\tfrom %s\n\tto   %s",
+                       name, ti_value, tc_value);
+       }
+    }
+}
+
 static char *
 skip_delay(char *s)
 {
@@ -1714,19 +2000,21 @@ ignore_delays(char *s)
 static bool
 similar_sgr(int num, char *a, char *b)
 {
-    static const char *names[] =
+#define DATA(name) { #name }
+    static const char names[][11] =
     {
-       "none"
-       ,"standout"
-       ,"underline"
-       ,"reverse"
-       ,"blink"
-       ,"dim"
-       ,"bold"
-       ,"invis"
-       ,"protect"
-       ,"altcharset"
+       DATA(none),
+       DATA(standout),
+       DATA(underline),
+       DATA(reverse),
+       DATA(blink),
+       DATA(dim),
+       DATA(bold),
+       DATA(invis),
+       DATA(protect),
+       DATA(altcharset),
     };
+#undef DATA
     char *base_a = a;
     char *base_b = b;
     int delaying = 0;
@@ -1886,20 +2174,17 @@ show_fkey_name(NAME_VALUE * data)
     }
 }
 
-/* other sanity-checks (things that we don't want in the normal
- * logic that reads a terminfo entry)
+/*
+ * A terminal entry may contain more than one keycode assigned to a given
+ * string (e.g., KEY_END and KEY_LL).  But curses will only return one (the
+ * last one assigned).
  */
 static void
-check_termtype(TERMTYPE *tp, bool literal)
+check_conflict(TERMTYPE *tp)
 {
     bool conflict = FALSE;
     unsigned j, k;
 
-    /*
-     * A terminal entry may contain more than one keycode assigned to
-     * a given string (e.g., KEY_END and KEY_LL).  But curses will only
-     * return one (the last one assigned).
-     */
     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
        char *check = calloc((size_t) (NUM_STRINGS(tp) + 1), sizeof(char));
        NAME_VALUE *given = get_fkey_list(tp);
@@ -1940,11 +2225,26 @@ check_termtype(TERMTYPE *tp, bool literal)
        free(given);
        free(check);
     }
+}
+
+/* other sanity-checks (things that we don't want in the normal
+ * logic that reads a terminfo entry)
+ */
+static void
+check_termtype(TERMTYPE *tp, bool literal)
+{
+    unsigned j;
+
+    check_conflict(tp);
 
     for_each_string(j, tp) {
        char *a = tp->Strings[j];
-       if (VALID_STRING(a))
+       if (VALID_STRING(a)) {
            check_params(tp, ExtStrname(tp, (int) j, strnames), a);
+           if (capdump) {
+               check_infotocap(tp, (int) j, a);
+           }
+       }
     }
 
     check_acs(tp);