ncurses 5.9 - patch 20140823
[ncurses.git] / progs / tabs.c
index 7588e8b2f0f5821b0f58ca4d3e13f0c28c9a0874..7ed3007549d8ac7ea2d6bba11481b5e3c6bb0cf4 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * Copyright (c) 2008 Free Software Foundation, Inc.                        *
+ * Copyright (c) 2008-2012,2013 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            *
 #define USE_LIBTINFO
 #include <progs.priv.h>
 
-MODULE_ID("$Id: tabs.c,v 1.11 2008/11/16 00:58:24 tom Exp $")
+MODULE_ID("$Id: tabs.c,v 1.34 2013/06/11 08:18:27 tom Exp $")
 
 static void usage(void) GCC_NORETURN;
 
+static char *prg_name;
 static int max_cols;
 
+static void
+failed(const char *s)
+{
+    perror(s);
+    ExitProgram(EXIT_FAILURE);
+}
+
 static int
 putch(int c)
 {
@@ -83,15 +91,22 @@ decode_tabs(const char *tab_list)
     int prior = 0;
     int ch;
 
+    if (result == 0)
+       failed("decode_tabs");
+
     while ((ch = *tab_list++) != '\0') {
        if (isdigit(UChar(ch))) {
            value *= 10;
            value += (ch - '0');
        } else if (ch == ',') {
            result[n] = value + prior;
-           if (n > 0 && value <= result[n - 1]) {
-               fprintf(stderr, "tab-stops are not in increasing order\n");
-               ExitProgram(EXIT_FAILURE);
+           if (n > 0 && result[n] <= result[n - 1]) {
+               fprintf(stderr,
+                       "%s: tab-stops are not in increasing order: %d %d\n",
+                       prg_name, value, result[n - 1]);
+               free(result);
+               result = 0;
+               break;
            }
            ++n;
            value = 0;
@@ -102,22 +117,26 @@ decode_tabs(const char *tab_list)
        }
     }
 
-    /*
-     * If there is only one value, then it is an option such as "-8".
-     */
-    if ((n == 0) && (value > 0)) {
-       int step = value;
-       while (n < max_cols - 1) {
-           result[n++] = value;
-           value += step;
+    if (result != 0) {
+       /*
+        * If there is only one value, then it is an option such as "-8".
+        */
+       if ((n == 0) && (value > 0)) {
+           int step = value;
+           value = 1;
+           while (n < max_cols - 1) {
+               result[n++] = value;
+               value += step;
+           }
        }
+
+       /*
+        * Add the last value, if any.
+        */
+       result[n++] = value + prior;
+       result[n] = 0;
     }
 
-    /*
-     * Add the last value, if any.
-     */
-    result[n++] = value;
-    result[n] = 0;
     return result;
 }
 
@@ -132,10 +151,11 @@ print_ruler(int *tab_list)
     for (n = 0; n < max_cols; n += 10) {
        int ch = 1 + (n / 10);
        char buffer[20];
-       sprintf(buffer, "----+----%c",
-               ((ch < 10)
-                ? (ch + '0')
-                : (ch + 'A' - 10)));
+       _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
+                   "----+----%c",
+                   ((ch < 10)
+                    ? (ch + '0')
+                    : (ch + 'A' - 10)));
        printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
     }
     putchar('\n');
@@ -180,6 +200,141 @@ write_tabs(int *tab_list)
     putchar('\n');
 }
 
+/*
+ * Trim leading/trailing blanks, as well as blanks after a comma.
+ * Convert embedded blanks to commas.
+ */
+static char *
+trimmed_tab_list(const char *source)
+{
+    char *result = strdup(source);
+    int ch, j, k, last;
+
+    if (result != 0) {
+       for (j = k = last = 0; result[j] != 0; ++j) {
+           ch = UChar(result[j]);
+           if (isspace(ch)) {
+               if (last == '\0') {
+                   continue;
+               } else if (isdigit(last) || last == ',') {
+                   ch = ',';
+               }
+           } else if (ch == ',') {
+               ;
+           } else {
+               if (last == ',')
+                   result[k++] = (char) last;
+               result[k++] = (char) ch;
+           }
+           last = ch;
+       }
+       result[k] = '\0';
+    }
+    return result;
+}
+
+static bool
+comma_is_needed(const char *source)
+{
+    bool result = FALSE;
+
+    if (source != 0) {
+       size_t len = strlen(source);
+       if (len != 0)
+           result = (source[len - 1] != ',');
+    } else {
+       result = FALSE;
+    }
+    return result;
+}
+
+/*
+ * Add a command-line parameter to the tab-list.  It can be blank- or comma-
+ * separated (or a mixture).  For simplicity, empty tabs are ignored, e.g.,
+ *     tabs 1,,6,11
+ *     tabs 1,6,11
+ * are treated the same.
+ */
+static const char *
+add_to_tab_list(char **append, const char *value)
+{
+    char *result = *append;
+    char *copied = trimmed_tab_list(value);
+
+    if (copied != 0 && *copied != '\0') {
+       const char *comma = ",";
+       size_t need = 1 + strlen(copied);
+
+       if (*copied == ',')
+           comma = "";
+       else if (!comma_is_needed(*append))
+           comma = "";
+
+       need += strlen(comma);
+       if (*append != 0)
+           need += strlen(*append);
+
+       result = malloc(need);
+       if (result == 0)
+           failed("add_to_tab_list");
+
+       *result = '\0';
+       if (*append != 0) {
+           _nc_STRCPY(result, *append, need);
+           free(*append);
+       }
+       _nc_STRCAT(result, comma, need);
+       _nc_STRCAT(result, copied, need);
+
+       *append = result;
+    }
+    return result;
+}
+
+/*
+ * Check for illegal characters in the tab-list.
+ */
+static bool
+legal_tab_list(const char *tab_list)
+{
+    bool result = TRUE;
+
+    if (tab_list != 0 && *tab_list != '\0') {
+       if (comma_is_needed(tab_list)) {
+           int n, ch;
+           for (n = 0; tab_list[n] != '\0'; ++n) {
+               ch = UChar(tab_list[n]);
+               if (!(isdigit(ch) || ch == ',' || ch == '+')) {
+                   fprintf(stderr,
+                           "%s: unexpected character found '%c'\n",
+                           prg_name, ch);
+                   result = FALSE;
+                   break;
+               }
+           }
+       } else {
+           fprintf(stderr, "%s: trailing comma found '%s'\n", prg_name, tab_list);
+           result = FALSE;
+       }
+    } else {
+       fprintf(stderr, "%s: no tab-list given\n", prg_name);
+       result = FALSE;
+    }
+    return result;
+}
+
+static char *
+skip_list(char *value)
+{
+    while (*value != '\0' &&
+          (isdigit(UChar(*value)) ||
+           isspace(UChar(*value)) ||
+           strchr("+,", UChar(*value)) != 0)) {
+       ++value;
+    }
+    return value;
+}
+
 static void
 usage(void)
 {
@@ -202,6 +357,7 @@ usage(void)
        ,"  -s       SNOBOL"
        ,"  -u       UNIVAC 1100 Assembler"
        ,"  -T name  use terminal type 'name'"
+       ,"  -V       print version"
        ,""
        ,"A tabstop-list is an ordered list of column numbers, e.g., 1,11,21"
        ,"or 1,+10,+10 which is the same."
@@ -218,15 +374,16 @@ usage(void)
 int
 main(int argc, char *argv[])
 {
-    int rc = EXIT_SUCCESS;
+    int rc = EXIT_FAILURE;
     bool debug = FALSE;
     bool no_op = FALSE;
     int n, ch;
     NCURSES_CONST char *term_name = 0;
-    const char *mar_list = 0;  /* ignored */
     char *append = 0;
     const char *tab_list = 0;
 
+    prg_name = _nc_rootname(argv[0]);
+
     if ((term_name = getenv("TERM")) == 0)
        term_name = "ansi+tabs";
 
@@ -238,23 +395,25 @@ main(int argc, char *argv[])
            while ((ch = *++option) != '\0') {
                switch (ch) {
                case 'a':
-                   switch (*option) {
+                   switch (*++option) {
+                   default:
                    case '\0':
                        tab_list = "1,10,16,36,72";
+                       option--;
                        /* Assembler, IBM S/370, first format */
                        break;
                    case '2':
                        tab_list = "1,10,16,40,72";
                        /* Assembler, IBM S/370, second format */
                        break;
-                   default:
-                       usage();
                    }
                    break;
                case 'c':
-                   switch (*option) {
+                   switch (*++option) {
+                   default:
                    case '\0':
                        tab_list = "1,8,12,16,20,55";
+                       option--;
                        /* COBOL, normal format */
                        break;
                    case '2':
@@ -265,8 +424,6 @@ main(int argc, char *argv[])
                        tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
                        /* COBOL compact format extended */
                        break;
-                   default:
-                       usage();
                    }
                    break;
                case 'd':       /* ncurses extension */
@@ -297,17 +454,22 @@ main(int argc, char *argv[])
                        term_name = option;
                    } else {
                        term_name = argv[n++];
+                       option--;
                    }
                    option += ((int) strlen(option)) - 1;
                    continue;
+               case 'V':
+                   puts(curses_version());
+                   ExitProgram(EXIT_SUCCESS);
                default:
                    if (isdigit(UChar(*option))) {
-                       tab_list = option;
-                       ++n;
+                       char *copy = strdup(option);
+                       *skip_list(copy) = '\0';
+                       tab_list = copy;
+                       option = skip_list(option) - 1;
                    } else {
                        usage();
                    }
-                   option += ((int) strlen(option)) - 1;
                    break;
                }
            }
@@ -316,33 +478,30 @@ main(int argc, char *argv[])
            while ((ch = *++option) != '\0') {
                switch (ch) {
                case 'm':
-                   mar_list = option;
+                   /*
+                    * The "+mXXX" option is unimplemented because only the long-obsolete
+                    * att510d implements smgl, which is needed to support
+                    * this option.
+                    */
                    break;
                default:
-                   usage();
+                   /* special case of relative stops separated by spaces? */
+                   if (option == argv[n] + 1) {
+                       tab_list = add_to_tab_list(&append, argv[n]);
+                   }
+                   break;
                }
            }
            break;
        default:
-           if (isdigit(*option)) {
-               if (append != 0) {
-                   if (tab_list != (const char *) append) {
-                       /* one of the predefined options was used */
-                       append = strdup(option);
-                       tab_list = append;
-                   } else {
-                       append = malloc(strlen(tab_list) + strlen(option) + 2);
-                       sprintf(append, "%s,%s", tab_list, option);
-                       free((char *) tab_list);
-                       tab_list = append;
-                   }
-               } else {
-                   append = strdup(option);
-                   tab_list = append;
+           if (append != 0) {
+               if (tab_list != (const char *) append) {
+                   /* one of the predefined options was used */
+                   free(append);
+                   append = 0;
                }
-           } else {
-               usage();
            }
+           tab_list = add_to_tab_list(&append, option);
            break;
        }
     }
@@ -354,14 +513,12 @@ main(int argc, char *argv[])
     if (!VALID_STRING(clear_all_tabs)) {
        fprintf(stderr,
                "%s: terminal type '%s' cannot reset tabs\n",
-               argv[0], term_name);
-       rc = EXIT_FAILURE;
+               prg_name, term_name);
     } else if (!VALID_STRING(set_tab)) {
        fprintf(stderr,
                "%s: terminal type '%s' cannot set tabs\n",
-               argv[0], term_name);
-       rc = EXIT_FAILURE;
-    } else {
+               prg_name, term_name);
+    } else if (legal_tab_list(tab_list)) {
        int *list = decode_tabs(tab_list);
 
        if (!no_op)
@@ -371,13 +528,19 @@ main(int argc, char *argv[])
            if (!no_op)
                do_tabs(list);
            if (debug) {
+               fflush(stderr);
+               printf("tabs %s\n", tab_list);
                print_ruler(list);
                write_tabs(list);
            }
            free(list);
+       } else if (debug) {
+           fflush(stderr);
+           printf("tabs %s\n", tab_list);
        }
+       rc = EXIT_SUCCESS;
     }
-    if (append)
+    if (append != 0)
        free(append);
     ExitProgram(rc);
 }