(WRITTEN_BY): Rename from AUTHORS.
[platform/upstream/coreutils.git] / src / seq.c
index aa78b9f..6f1cbc7 100644 (file)
--- a/src/seq.c
+++ b/src/seq.c
@@ -1,5 +1,5 @@
 /* seq - print sequence of numbers to standard output.
-   Copyright (C) 94, 1995 Free Software Foundation, Inc.
+   Copyright (C) 1994-2003 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -12,8 +12,8 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 /* Written by Ulrich Drepper.  */
 
 #include <getopt.h>
 #include <math.h>
 #include <stdio.h>
+#include <sys/types.h>
 
 #include "system.h"
 #include "error.h"
+#include "xstrtol.h"
 #include "xstrtod.h"
 
-static double scan_double_arg __P ((const char *arg));
-static int check_format __P ((const char *format_string));
-static char *get_width_format __P ((void));
-static int print_numbers __P ((const char *format_str));
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "seq"
+
+#define WRITTEN_BY _("Written by Ulrich Drepper.")
 
 /* If nonzero print all number with equal width.  */
 static int equal_width;
 
-/* The printf(3) format used for output.  */
-static char *format_str;
-
-/* The starting number.  */
-static double first;
-
 /* The name that this program was run with.  */
 char *program_name;
 
@@ -51,14 +47,15 @@ static char *separator;
 /* FIXME: make this an option.  */
 static char *terminator = "\n";
 
-/* If nonzero, display usage information and exit.  */
-static int show_help;
+/* The representation of the decimal point in the current locale.
+   Always "." if the localeconv function is not supported.  */
+static char *decimal_point = ".";
 
-/* If nonzero, print the version on standard output and exit.  */
-static int show_version;
+/* The starting number.  */
+static double first;
 
 /* The increment.  */
-static double step;
+static double step = 1.0;
 
 /* The last number.  */
 static double last;
@@ -67,166 +64,47 @@ static struct option const long_options[] =
 {
   { "equal-width", no_argument, NULL, 'w'},
   { "format", required_argument, NULL, 'f'},
-  { "help", no_argument, &show_help, 1},
   { "separator", required_argument, NULL, 's'},
-  { "version", no_argument, &show_version, 1},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
   { NULL, 0, NULL, 0}
 };
 
-static void
+void
 usage (int status)
 {
   if (status != 0)
-    (void) fprintf (stderr, _("Try `%s --help' for more information.\n"),
-                   program_name);
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
   else
     {
-      (void) printf (_("\
-Usage: %s [OPTION]... [FIRST [step]] LAST\n\
-"), program_name);
-      (void) printf (_("\
+      printf (_("\
+Usage: %s [OPTION]... LAST\n\
+  or:  %s [OPTION]... FIRST LAST\n\
+  or:  %s [OPTION]... FIRST INCREMENT LAST\n\
+"), program_name, program_name, program_name);
+      fputs (_("\
+Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
 \n\
-  -f, --format FORMAT      use printf(3) style FORMAT (default: %%g)\n\
-      --help               display this help and exit\n\
-  -s, --separator STRING   use STRING for separating numbers (default: \\n)\n\
-      --version            output version information and exit\n\
+  -f, --format=FORMAT      use printf style floating-point FORMAT (default: %g)\n\
+  -s, --separator=STRING   use STRING to separate numbers (default: \\n)\n\
   -w, --equal-width        equalize width by padding with leading zeroes\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      fputs (_("\
 \n\
-  FROM, STEP, TO are interpreted as floating point.  STEP should be > 0 if\n\
-  FROM is smaller than TO and vice versa.  When given, the FORMAT argument\n\
-  must contain exactly one of the float output formats %%e, %%f, or %%g.\n\
-"));
+If FIRST or INCREMENT is omitted, it defaults to 1.\n\
+FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
+INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
+otherwise.  When given, the FORMAT argument must contain exactly one of\n\
+the printf-style, floating point output formats %e, %f, %g\n\
+"), stdout);
+      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
     }
   exit (status);
 }
 
-int
-main (int argc, char **argv)
-{
-  int errs;
-  int optc;
-  int step_is_set;
-
-  program_name = argv[0];
-  setlocale (LC_ALL, "");
-  bindtextdomain (PACKAGE, LOCALEDIR);
-  textdomain (PACKAGE);
-
-  equal_width = 0;
-  format_str = NULL;
-  separator = "\n";
-  first = 1.0;
-  step_is_set = 0;
-
-  /* We have to handle negative numbers in the command line but this
-     conflicts with the command line arguments.  So the getopt mode is
-     REQUIRE_ORDER (the '+' in the format string) and it abort on the
-     first non-option or negative number.  */
-  while ((optc = getopt_long (argc, argv, "+0123456789f:s:w", long_options,
-                             (int *) 0)) != EOF)
-    {
-      if ('0' <= optc && optc <= '9')
-       {
-         /* means negative number */
-         break;
-       }
-
-      switch (optc)
-       {
-       case 0:
-         break;
-
-       case 'f':
-         format_str = optarg;
-         break;
-
-       case 's':
-         separator = optarg;
-         break;
-
-       case 'w':
-         equal_width = 1;
-         break;
-
-       default:
-         usage (1);
-         /* NOTREACHED */
-       }
-    }
-
-  if (show_version)
-    {
-      (void) printf ("seq - %s\n", PACKAGE_VERSION);
-      exit (0);
-    }
-
-  if (show_help)
-    {
-      usage (0);
-      /* NOTREACHED */
-    }
-
-  if (optind >= argc)
-    {
-      error (0, 0, _("too few arguments"));
-      usage (1);
-      /* NOTREACHED */
-    }
-  last = scan_double_arg (argv[optind++]);
-
-  if (optind < argc)
-    {
-      first = last;
-      last = scan_double_arg (argv[optind++]);
-
-      if (optind < argc)
-       {
-         step = last;
-         step_is_set = 1;
-         last = scan_double_arg (argv[optind++]);
-
-         if (optind < argc)
-           {
-             usage (1);
-             /* NOTREACHED */
-           }
-       }
-    }
-
-  if (format_str != NULL && equal_width)
-    {
-      error (0, 0, _("\
-format string may not be specified when printing equal width strings"));
-      usage (1);
-    }
-
-  if (!step_is_set)
-    {
-      step = first <= last ? 1.0 : -1.0;
-    }
-
-  if (format_str != NULL)
-    {
-      if (!check_format (format_str))
-       {
-         error (0, 0, _("invalid format string: `%s'"), format_str);
-         usage (1);
-       }
-    }
-  else
-    {
-      if (equal_width)
-       format_str = get_width_format ();
-      else
-       format_str = "%g";
-    }
-
-  errs = print_numbers (format_str);
-
-  exit (errs);
-  /* NOTREACHED */
-}
-
 /* Read a double value from the command line.
    Return if the string is correct else signal error.  */
 
@@ -238,64 +116,87 @@ scan_double_arg (const char *arg)
   if (xstrtod (arg, NULL, &ret_val))
     {
       error (0, 0, _("invalid floating point argument: %s"), arg);
-      usage (1);
-      /* NOTREACHED */
+      usage (EXIT_FAILURE);
     }
 
   return ret_val;
 }
 
-/* Check whether the format string is valid for a single double
-   argument.
-   Return 0 if not, 1 if correct.  */
+/* Check whether the format string is valid for a single `double'
+   argument.  Return 0 if not, 1 if correct. */
 
 static int
-check_format (const char *format_string)
+valid_format (const char *fmt)
 {
-  while (*format_string != '\0')
+  while (*fmt != '\0')
     {
-      if (*format_string == '%')
+      if (*fmt == '%')
        {
-         format_string++;
-         if (*format_string != '%')
+         fmt++;
+         if (*fmt != '%')
            break;
        }
 
-      format_string++;
+      fmt++;
     }
-  if (*format_string == '\0')
+  if (*fmt == '\0')
     return 0;
 
-  format_string += strspn (format_string, "-+#0");
-  if (isdigit (*format_string))
+  fmt += strspn (fmt, "-+#0 '");
+  if (ISDIGIT (*fmt) || *fmt == '.')
     {
-      format_string += strspn (format_string, "012345789");
+      fmt += strspn (fmt, "0123456789");
 
-      if (*format_string == '.')
-       format_string += strspn (++format_string, "0123456789");
+      if (*fmt == '.')
+       {
+         ++fmt;
+         fmt += strspn (fmt, "0123456789");
+       }
     }
 
-  if (*format_string != 'e' && *format_string != 'f' &&
-      *format_string != 'g')
+  if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g'))
     return 0;
 
-  format_string++;
-  while (*format_string != '\0')
+  fmt++;
+  while (*fmt != '\0')
     {
-      if (*format_string == '%')
+      if (*fmt == '%')
        {
-         format_string++;
-         if (*format_string != '%')
+         fmt++;
+         if (*fmt != '%')
            return 0;
        }
 
-      format_string++;
+      fmt++;
     }
 
   return 1;
 }
 
-#if defined (HAVE_RINT) && defined (HAVE_MODF) && defined (HAVE_FLOOR)
+/* Actually print the sequence of numbers in the specified range, with the
+   given or default stepping and format.  */
+static int
+print_numbers (const char *fmt)
+{
+  int i;
+
+  for (i = 0; /* empty */; i++)
+    {
+      double x = first + i * step;
+      if (step < 0 ? x < last : last < x)
+       break;
+      if (i)
+       fputs (separator, stdout);
+      printf (fmt, x);
+    }
+
+  if (i)
+    fputs (terminator, stdout);
+
+  return 0;
+}
+
+#if HAVE_RINT && HAVE_MODF && HAVE_FLOOR
 
 /* Return a printf-style format string with which all selected numbers
    will format to strings of the same width.  */
@@ -322,51 +223,60 @@ get_width_format ()
       max_val = first + step * floor ((last - first) / step);
     }
 
-  (void) sprintf (buffer, "%g", rint (max_val));
-  if (buffer[strspn (buffer, "0123456789")] != '\0')
+  sprintf (buffer, "%g", rint (max_val));
+  if (buffer[strspn (buffer, "-0123456789")] != '\0')
     return "%g";
   width1 = strlen (buffer);
 
   if (min_val < 0.0)
     {
-      (void) sprintf (buffer, "%g", rint (min_val));
+      double int_min_val = rint (min_val);
+      sprintf (buffer, "%g", int_min_val);
       if (buffer[strspn (buffer, "-0123456789")] != '\0')
        return "%g";
-      width2 = strlen (buffer);
+      /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'.
+        On others, it is just `0'.  The former results in better output.  */
+      width2 = (int_min_val == 0 ? 2 : strlen (buffer));
 
       width1 = width1 > width2 ? width1 : width2;
     }
   full_width = width1;
 
-  (void) sprintf (buffer, "%g", 1.0 + modf (min_val, &temp));
+  sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp));
   width1 = strlen (buffer);
   if (width1 == 1)
     width1 = 0;
   else
     {
-      if (buffer[0] != '1' || buffer[1] != '.' ||
-         buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
+      if (buffer[0] != '1'
+         /* FIXME: assumes that decimal_point is a single character
+            string.  */
+         || buffer[1] != decimal_point[0]
+         || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
        return "%g";
       width1 -= 2;
     }
 
-  (void) sprintf (buffer, "%g", 1.0 + modf (step, &temp));
+  sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp));
   width2 = strlen (buffer);
   if (width2 == 1)
     width2 = 0;
   else
     {
-      if (buffer[0] != '1' || buffer[1] != '.' ||
-         buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
+      if (buffer[0] != '1'
+         /* FIXME: assumes that decimal_point is a single character
+            string.  */
+         || buffer[1] != decimal_point[0]
+         || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
        return "%g";
       width2 -= 2;
     }
   frac_width = width1 > width2 ? width1 : width2;
 
   if (frac_width)
-    (void) sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
+    sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
   else
-    (void) sprintf (buffer, "%%0%dg", full_width);
+    sprintf (buffer, "%%0%dg", full_width);
 
   return buffer;
 }
@@ -383,56 +293,130 @@ get_width_format (void)
 
 #endif
 
-/* Actually print the sequence of numbers in the specified range, with the
-   given or default stepping and format.  */
-static int
-print_numbers (const char *format_str)
+int
+main (int argc, char **argv)
 {
-  if (first > last)
-    {
-      int i;
+  int errs;
+  int optc;
+
+  /* The printf(3) format used for output.  */
+  char *format_str = NULL;
+
+  initialize_main (&argc, &argv);
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
 
-      if (step >= 0)
+  atexit (close_stdout);
+
+  equal_width = 0;
+  separator = "\n";
+  first = 1.0;
+
+  /* Figure out the locale's idea of a decimal point.  */
+#if HAVE_LOCALECONV
+  {
+    struct lconv *locale;
+
+    locale = localeconv ();
+    /* Paranoia.  */
+    if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
+      decimal_point = locale->decimal_point;
+  }
+#endif
+
+  /* We have to handle negative numbers in the command line but this
+     conflicts with the command line arguments.  So explicitly check first
+     whether the next argument looks like a negative number.  */
+  while (optind < argc)
+    {
+      if (argv[optind][0] == '-'
+         && ((optc = argv[optind][1]) == decimal_point[0]
+             || ISDIGIT (optc)))
        {
-         error (0, 0, _("invalid increment: %g"), step);
-         usage (1);
-         /* NOTREACHED */
+         /* means negative number */
+         break;
        }
 
-      for (i = 0; /* empty */; i++)
+      optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
+      if (optc == -1)
+       break;
+
+      switch (optc)
        {
-         double x = first + i * step;
-         printf (format_str, x);
+       case 0:
+         break;
 
-         if (x <= last)
-           break;
+       case 'f':
+         format_str = optarg;
+         break;
 
-         fputs (separator, stdout);
+       case 's':
+         separator = optarg;
+         break;
+
+       case 'w':
+         equal_width = 1;
+         break;
+
+       case_GETOPT_HELP_CHAR;
+
+       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
+
+       default:
+         usage (EXIT_FAILURE);
        }
     }
-  else
+
+  if (argc - optind < 1)
     {
-      int i;
+      error (0, 0, _("too few arguments"));
+      usage (EXIT_FAILURE);
+    }
 
-      if (step <= 0)
-       {
-         error (0, 0, _("invalid increment: %g"), step);
-         usage (1);
-         /* NOTREACHED */
-       }
+  if (3 < argc - optind)
+    {
+      error (0, 0, _("too many arguments"));
+      usage (EXIT_FAILURE);
+    }
 
-      for (i = 0; /* empty */; i++)
-       {
-         double x = first + i * step;
-         printf (format_str, x);
+  if (format_str && !valid_format (format_str))
+    {
+      error (0, 0, _("invalid format string: `%s'"), format_str);
+      usage (EXIT_FAILURE);
+    }
 
-         if (x >= last)
-           break;
+  last = scan_double_arg (argv[optind++]);
 
-         fputs (separator, stdout);
+  if (optind < argc)
+    {
+      first = last;
+      last = scan_double_arg (argv[optind++]);
+
+      if (optind < argc)
+       {
+         step = last;
+         last = scan_double_arg (argv[optind++]);
        }
     }
-  fputs (terminator, stdout);
 
-  return 0;
+  if (format_str != NULL && equal_width)
+    {
+      error (0, 0, _("\
+format string may not be specified when printing equal width strings"));
+      usage (EXIT_FAILURE);
+    }
+
+  if (format_str == NULL)
+    {
+      if (equal_width)
+       format_str = get_width_format ();
+      else
+       format_str = "%g";
+    }
+
+  errs = print_numbers (format_str);
+
+  exit (errs);
 }