]> sjero.net Git - wget/blobdiff - src/utils.c
[svn] Include ETA information in dot progress.
[wget] / src / utils.c
index 9773249687159834e03293d947e9b3fd7a8f7f26..9c8beb10f5e05062dd43896fa48ba5183ef5ff70 100644 (file)
@@ -58,6 +58,9 @@ so, delete this exception statement from your version.  */
 #include <fcntl.h>
 #include <assert.h>
 #include <stdarg.h>
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
 
 /* For TIOCGWINSZ and friends: */
 #ifdef HAVE_SYS_IOCTL_H
@@ -1161,81 +1164,107 @@ free_keys_and_values (struct hash_table *ht)
 }
 
 \f
-/* Add thousand separators to a number already in string form.  Used
-   by with_thousand_seps and with_thousand_seps_large.  */
-
-static char *
-add_thousand_seps (const char *repr)
-{
-  static char outbuf[48];
-  int i, i1, mod;
-  char *outptr;
-  const char *inptr;
+/* Get grouping data, the separator and grouping info, by calling
+   localeconv().  The information is cached after the first call to
+   the function.
 
-  /* Reset the pointers.  */
-  outptr = outbuf;
-  inptr = repr;
+   In locales that don't set a thousand separator (such as the "C"
+   locale), this forces it to be ",".  We are now only showing
+   thousand separators in one place, so this shouldn't be a problem in
+   practice.  */
 
-  /* Ignore the sign for the purpose of adding thousand
-     separators.  */
-  if (*inptr == '-')
-    {
-      *outptr++ = '-';
-      ++inptr;
-    }
-  /* How many digits before the first separator?  */
-  mod = strlen (inptr) % 3;
-  /* Insert them.  */
-  for (i = 0; i < mod; i++)
-    *outptr++ = inptr[i];
-  /* Now insert the rest of them, putting separator before every
-     third digit.  */
-  for (i1 = i, i = 0; inptr[i1]; i++, i1++)
+static void
+get_grouping_data (const char **sep, const char **grouping)
+{
+  static const char *cached_sep;
+  static const char *cached_grouping;
+  static bool initialized;
+  if (!initialized)
     {
-      if (i % 3 == 0 && i1 != 0)
-       *outptr++ = ',';
-      *outptr++ = inptr[i1];
+      /* Get the grouping info from the locale. */
+      struct lconv *lconv = localeconv ();
+      cached_sep = lconv->thousands_sep;
+      cached_grouping = lconv->grouping;
+      if (!*cached_sep)
+       {
+         /* Many locales (such as "C" or "hr_HR") don't specify
+            grouping, which we still want to use it for legibility.
+            In those locales set the sep char to ',', unless that
+            character is used for decimal point, in which case set it
+            to ".".  */
+         if (*lconv->decimal_point != ',')
+           cached_sep = ",";
+         else
+           cached_sep = ".";
+         cached_grouping = "\x03";
+       }
+      initialized = true;
     }
-  /* Zero-terminate the string.  */
-  *outptr = '\0';
-  return outbuf;
+  *sep = cached_sep;
+  *grouping = cached_grouping;
 }
 
-/* Return a static pointer to the number printed with thousand
-   separators inserted at the right places.  */
+/* Return a printed representation of N with thousand separators.
+   This should respect locale settings, with the exception of the "C"
+   locale which mandates no separator, but we use one anyway.
 
-char *
-with_thousand_seps (wgint l)
+   Unfortunately, we cannot use %'d (in fact it would be %'j) to get
+   the separators because it's too non-portable, and it's hard to test
+   for this feature at configure time.  Besides, it wouldn't work in
+   the "C" locale, which many Unix users still work in.  */
+
+const char *
+with_thousand_seps (wgint n)
 {
-  char inbuf[24];
-  /* Print the number into the buffer.  */
-  number_to_string (inbuf, l);
-  return add_thousand_seps (inbuf);
-}
+  static char outbuf[48];
+  char *p = outbuf + sizeof outbuf;
 
-/* Write a string representation of LARGE_INT NUMBER into the provided
-   buffer.
+  /* Info received from locale */
+  const char *grouping, *sep;
+  int seplen;
 
-   It would be dangerous to use sprintf, because the code wouldn't
-   work on a machine with gcc-provided long long support, but without
-   libc support for "%lld".  However, such old systems platforms
-   typically lack snprintf and will end up using our version, which
-   does support "%lld" whereever long longs are available.  */
+  /* State information */
+  int i = 0, groupsize;
+  const char *atgroup;
 
-static void
-large_int_to_string (char *buffer, int bufsize, LARGE_INT number)
-{
-  snprintf (buffer, bufsize, LARGE_INT_FMT, number);
-}
+  bool negative = n < 0;
 
-/* The same as with_thousand_seps, but works on LARGE_INT.  */
+  /* Initialize grouping data. */
+  get_grouping_data (&sep, &grouping);
+  seplen = strlen (sep);
+  atgroup = grouping;
+  groupsize = *atgroup++;
 
-char *
-with_thousand_seps_large (LARGE_INT l)
-{
-  char inbuf[48];
-  large_int_to_string (inbuf, sizeof (inbuf), l);
-  return add_thousand_seps (inbuf);
+  /* This will overflow on WGINT_MIN, but we're not using this to
+     print negative numbers anyway.  */
+  if (negative)
+    n = -n;
+
+  /* Write the number into the buffer, backwards, inserting the
+     separators as necessary.  */
+  *--p = '\0';
+  while (1)
+    {
+      *--p = n % 10 + '0';
+      n /= 10;
+      if (n == 0)
+       break;
+      /* Prepend SEP to every groupsize'd digit and get new groupsize.  */
+      if (++i == groupsize)
+       {
+         if (seplen == 1)
+           *--p = *sep;
+         else
+           memcpy (p -= seplen, sep, seplen);
+         i = 0;
+         if (*atgroup)
+           groupsize = *atgroup++;
+       }
+    }
+  if (negative)
+    *--p = '-';
+
+  return p;
 }
 
 /* N, a byte quantity, is converted to a human-readable abberviated
@@ -1249,14 +1278,14 @@ with_thousand_seps_large (LARGE_INT l)
    usually improves readability."
 
    This intentionally uses kilobyte (KB), megabyte (MB), etc. in their
-   original computer science meaning of "powers of 1024".  Powers of
+   original computer-related meaning of "powers of 1024".  Powers of
    1000 would be useless since Wget already displays sizes with
    thousand separators.  We don't use the "*bibyte" names invented in
    1998, and seldom used in practice.  Wikipedia's entry on kilobyte
    discusses this in some detail.  */
 
 char *
-human_readable (wgint n)
+human_readable (HR_NUMTYPE n)
 {
   /* These suffixes are compatible with those of GNU `ls -lh'. */
   static char powers[] =
@@ -1286,19 +1315,16 @@ human_readable (wgint n)
       /* At each iteration N is greater than the *subsequent* power.
         That way N/1024.0 produces a decimal number in the units of
         *this* power.  */
-      if ((n >> 10) < 1024 || i == countof (powers) - 1)
+      if ((n / 1024) < 1024 || i == countof (powers) - 1)
        {
-         /* Must cast to long first because MS VC can't directly cast
-            __int64 to double.  (This is safe because N is known to
-            be <2**20.)  */
-         double val = (double) (long) n / 1024.0;
+         double val = n / 1024.0;
          /* Print values smaller than 10 with one decimal digits, and
             others without any decimals.  */
          snprintf (buf, sizeof (buf), "%.*f%c",
                    val < 10 ? 1 : 0, val, powers[i]);
          return buf;
        }
-      n >>= 10;
+      n /= 1024;
     }
   return NULL;                 /* unreached */
 }
@@ -1356,7 +1382,7 @@ numdigit (wgint number)
 #elif SIZEOF_LONG_LONG >= SIZEOF_WGINT
 # define SPRINTF_WGINT(buf, n) sprintf (buf, "%lld", (long long) (n))
 #elif defined(WINDOWS)
-# define SPRINTF_WGINT(buf, n) sprintf (buf, "%I64", (__int64) (n))
+# define SPRINTF_WGINT(buf, n) sprintf (buf, "%I64d", (__int64) (n))
 #else
 # define SPRINTF_WGINT(buf, n) sprintf (buf, "%j", (intmax_t) (n))
 #endif
@@ -1451,6 +1477,7 @@ number_to_string (char *buffer, wgint number)
 
 #undef PR
 #undef W
+#undef SPRINTF_WGINT
 #undef DIGITS_1
 #undef DIGITS_2
 #undef DIGITS_3
@@ -1595,13 +1622,13 @@ random_number (int max)
 /* Return a random uniformly distributed floating point number in the
    [0, 1) range.  The precision of returned numbers is 9 digits.
 
-   Modify this to use erand48() where available!  */
+   Modify this to use drand48() where available!  */
 
 double
 random_float (void)
 {
-  /* We can't rely on any specific value of RAND_MAX, but I'm pretty
-     sure it's greater than 1000.  */
+  /* We can't rely on any specific value of RAND_MAX, but it must
+     always be greater than 1000.  */
   int rnd1 = random_number (1000);
   int rnd2 = random_number (1000);
   int rnd3 = random_number (1000);
@@ -2012,3 +2039,38 @@ stable_sort (void *base, size_t nmemb, size_t size,
       mergesort_internal (base, temp, size, 0, nmemb - 1, cmpfun);
     }
 }
+\f
+/* Print a decimal number.  If it is equal to or larger than ten, the
+   number is rounded.  Otherwise it is printed with one significant
+   digit without trailing zeros and with no more than three fractional
+   digits total.  For example, 0.1 is printed as "0.1", 0.035 is
+   printed as "0.04", 0.0091 as "0.009", and 0.0003 as simply "0".
+
+   This is useful for displaying durations because it provides
+   order-of-magnitude information without unnecessary clutter --
+   long-running downloads are shown without the fractional part, and
+   short ones still retain one significant digit.  */
+
+const char *
+print_decimal (double number)
+{
+  static char buf[32];
+  double n = number >= 0 ? number : -number;
+
+  if (n >= 9.95)
+    /* Cut off at 9.95 because the below %.1f would round 9.96 to
+       "10.0" instead of "10".  OTOH 9.94 will print as "9.9".  */
+    snprintf (buf, sizeof buf, "%.0f", number);
+  else if (n >= 0.95)
+    snprintf (buf, sizeof buf, "%.1f", number);
+  else if (n >= 0.001)
+    snprintf (buf, sizeof buf, "%.1g", number);
+  else if (n >= 0.0005)
+    /* round [0.0005, 0.001) to 0.001 */
+    snprintf (buf, sizeof buf, "%.3f", number);
+  else
+    /* print numbers close to 0 as 0, not 0.000 */
+    strcpy (buf, "0");
+
+  return buf;
+}