>From a6c62fba1867f1df9b8d2a8c9264606784fddc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draig=20Brady?= Date: Wed, 30 Apr 2014 15:05:15 +0100 Subject: [PATCH] numfmt: support zero padding using --format="%010f" * src/numfmt.c (setup_padding_buffer): Simplify the code by not explicitly dealing with heap exhaustion. (parse_format_string): Likewise. Handle multiple grouping modifiers as does the standard printf. Handle the new leading zero --format modifier. (double_to_human): Use more defensive coding against overwriting stack buffers. Honor the leading zeros width. (usage): Mention the leading zero --format modifier. (main): Allow --padding in combo with a --format (width), as the number of leading zeros are useful independent of the main field width. * doc/coreutils.texi (numfmt invocation): Likewise. * tests/misc/numfmt.pl: Add new test cases. * NEWS: Mention the improvement. --- NEWS | 3 ++ doc/coreutils.texi | 6 ++- src/numfmt.c | 94 ++++++++++++++++++++++++++++++++------------------ tests/misc/numfmt.pl | 25 +++++++++++--- 4 files changed, 87 insertions(+), 41 deletions(-) diff --git a/NEWS b/NEWS index 7855a48..904aace 100644 --- a/NEWS +++ b/NEWS @@ -66,6 +66,9 @@ GNU coreutils NEWS -*- outline -*- causing name look-up errors. Also look-ups are first done outside the chroot, in case the look-up within the chroot fails due to library conflicts etc. + numfmt supports zero padding of numbers using the standard --printf + syntax of a leading zero, for example --format="%010f". + shred now supports multiple passes on GNU/Linux tape devices by rewinding the tape before each pass, avoids redundant writes to empty files, uses direct I/O for all passes where possible, and attempts to clear diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 12002fc..a949ffc 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -2293,10 +2293,12 @@ Convert the number in input field @var{n} (default: 1). @item address@hidden @opindex --format Use printf-style floating FORMAT string. The @var{format} string must contain -one @samp{%f} directive, optionally with @samp{'}, @samp{-}, or width +one @samp{%f} directive, optionally with @samp{'}, @samp{-}, @samp{0}, or width modifiers. The @samp{'} modifier will enable @option{--grouping}, the @samp{-} modifier will enable left-aligned @option{--padding} and the width modifier will -enable right-aligned @option{--padding}. +enable right-aligned @option{--padding}. The @samp{0} width modifier +(without the @samp{-} modifier) will generate leading zeros on the number, +up to the specified width. @item address@hidden @opindex --from diff --git a/src/numfmt.c b/src/numfmt.c index 63411f3..c744875 100644 --- a/src/numfmt.c +++ b/src/numfmt.c @@ -169,6 +169,7 @@ static int grouping = 0; static char *padding_buffer = NULL; static size_t padding_buffer_size = 0; static long int padding_width = 0; +static long int zero_padding_width = 0; static const char *format_str = NULL; static char *format_str_prefix = NULL; static char *format_str_suffix = NULL; @@ -272,7 +273,7 @@ suffix_power (const char suf) } static inline const char * -suffix_power_character (unsigned int power) +suffix_power_char (unsigned int power) { switch (power) { @@ -705,6 +706,21 @@ double_to_human (long double val, int precision, char *buf, size_t buf_size, enum scale_type scale, int group, enum round_type round) { + int num_size; + char fmt[64]; + verify (sizeof (fmt) > (INT_BUFSIZE_BOUND (zero_padding_width) + + INT_BUFSIZE_BOUND (precision) + + 10 /* for %.Lf etc. */)); + + char *pfmt = fmt; + *pfmt++ = '%'; + + if (group) + *pfmt++ = '\''; + + if (zero_padding_width) + pfmt += snprintf (pfmt, sizeof (fmt) - 1, "0%ld", zero_padding_width); + devmsg ("double_to_human:\n"); if (scale == scale_none) @@ -717,9 +733,10 @@ double_to_human (long double val, int precision, " no scaling, returning (grouped) value: %'.*Lf\n" : " no scaling, returning value: %.*Lf\n", precision, val); - int i = snprintf (buf, buf_size, (group) ? "%'.*Lf" : "%.*Lf", - precision, val); - if (i < 0 || i >= (int) buf_size) + stpcpy (pfmt, ".*Lf"); + + num_size = snprintf (buf, buf_size, fmt, precision, val); + if (num_size < 0 || num_size >= (int) buf_size) error (EXIT_FAILURE, 0, _("failed to prepare value '%Lf' for printing"), val); return; @@ -761,11 +778,16 @@ double_to_human (long double val, int precision, devmsg (" after rounding, value=%Lf * %0.f ^ %d\n", val, scale_base, power); - snprintf (buf, buf_size, (show_decimal_point) ? "%.1Lf%s" : "%.0Lf%s", - val, suffix_power_character (power)); + stpcpy (pfmt, show_decimal_point ? ".1Lf%s" : ".0Lf%s"); + + /* buf_size - 1 used here to ensure place for possible scale_IEC_I suffix. */ + num_size = snprintf (buf, buf_size - 1, fmt, val, suffix_power_char (power)); + if (num_size < 0 || num_size >= (int) buf_size - 1) + error (EXIT_FAILURE, 0, + _("failed to prepare value '%Lf' for printing"), val); if (scale == scale_IEC_I && power > 0) - strncat (buf, "i", buf_size - strlen (buf) - 1); + strncat (buf, "i", buf_size - num_size - 1); devmsg (" returning value: %s\n", quote (buf)); @@ -798,10 +820,7 @@ setup_padding_buffer (size_t min_size) return; padding_buffer_size = min_size + 1; - padding_buffer = realloc (padding_buffer, padding_buffer_size); - if (!padding_buffer) - error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"), - padding_buffer_size); + padding_buffer = xrealloc (padding_buffer, padding_buffer_size); } void @@ -906,8 +925,8 @@ UNIT options:\n"), stdout); fputs (_("\n\ FORMAT must be suitable for printing one floating-point argument '%f'.\n\ Optional quote (%'f) will enable --grouping (if supported by current locale).\n\ -Optional width value (%10f) will pad output. Optional negative width values\n\ -(%-10f) will left-pad output.\n\ +Optional width value (%10f) will pad output. Optional zero (%010f) width\n\ +will zero pad the number. Optional negative values (%-10f) will left align.\n\ "), stdout); printf (_("\n\ @@ -967,6 +986,7 @@ parse_format_string (char const *fmt) size_t suffix_pos; long int pad = 0; char *endptr = NULL; + bool zero_padding = false; for (i = 0; !(fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1) { @@ -977,13 +997,24 @@ parse_format_string (char const *fmt) } i++; - i += strspn (fmt + i, " "); - if (fmt[i] == '\'') + while (true) { - grouping = 1; - i++; + size_t skip = strspn (fmt + i, " "); + i += skip; + if (fmt[i] == '\'') + { + grouping = 1; + i++; + } + else if (fmt[i] == '0') + { + zero_padding = true; + i++; + } + else if (! skip) + break; } - i += strspn (fmt + i, " "); + errno = 0; pad = strtol (fmt + i, &endptr, 10); if (errno == ERANGE) @@ -992,6 +1023,9 @@ parse_format_string (char const *fmt) if (endptr != (fmt + i) && pad != 0) { + if (debug && padding_width && !(zero_padding && pad > 0)) + error (0, 0, _("--format padding overridding --padding")); + if (pad < 0) { padding_alignment = MBS_ALIGN_LEFT; @@ -999,8 +1033,12 @@ parse_format_string (char const *fmt) } else { - padding_width = pad; + if (zero_padding) + zero_padding_width = pad; + else + padding_width = pad; } + } i = endptr - fmt; @@ -1009,7 +1047,7 @@ parse_format_string (char const *fmt) if (fmt[i] != 'f') error (EXIT_FAILURE, 0, _("invalid format %s," - " directive must be %%['][-][N]f"), + " directive must be %%[0]['][-][N]f"), quote (fmt)); i++; suffix_pos = i; @@ -1020,19 +1058,9 @@ parse_format_string (char const *fmt) quote (fmt)); if (prefix_len) - { - format_str_prefix = xstrndup (fmt, prefix_len); - if (!format_str_prefix) - error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"), - prefix_len + 1); - } + format_str_prefix = xstrndup (fmt, prefix_len); if (fmt[suffix_pos] != '\0') - { - format_str_suffix = strdup (fmt + suffix_pos); - if (!format_str_suffix) - error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"), - strlen (fmt + suffix_pos)); - } + format_str_suffix = xstrdup (fmt + suffix_pos); devmsg ("format String:\n input: %s\n grouping: %s\n" " padding width: %ld\n alignment: %s\n" @@ -1462,8 +1490,6 @@ main (int argc, char **argv) if (format_str != NULL && grouping) error (EXIT_FAILURE, 0, _("--grouping cannot be combined with --format")); - if (format_str != NULL && padding_width > 0) - error (EXIT_FAILURE, 0, _("--padding cannot be combined with --format")); /* Warn about no-op. */ if (debug && scale_from == scale_none && scale_to == scale_none diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl index ca3c896..dfb4b2e 100755 --- a/tests/misc/numfmt.pl +++ b/tests/misc/numfmt.pl @@ -695,11 +695,11 @@ my @Tests = {EXIT=>1}], ['fmt-err-4', '--format "%d"', {ERR=>"$prog: invalid format '%d', " . - "directive must be %['][-][N]f\n"}, + "directive must be %[0]['][-][N]f\n"}, {EXIT=>1}], ['fmt-err-5', '--format "% -43 f"', {ERR=>"$prog: invalid format '% -43 f', " . - "directive must be %['][-][N]f\n"}, + "directive must be %[0]['][-][N]f\n"}, {EXIT=>1}], ['fmt-err-6', '--format "%f %f"', {ERR=>"$prog: format '%f %f' has too many % directives\n"}, @@ -708,9 +708,6 @@ my @Tests = {ERR=>"$prog: invalid format '%123456789012345678901234567890f'". " (width overflow)\n"}, {EXIT=>1}], - ['fmt-err-8', '--format "%f" --padding 20', - {ERR=>"$prog: --padding cannot be combined with --format\n"}, - {EXIT=>1}], ['fmt-err-9', '--format "%f" --grouping', {ERR=>"$prog: --grouping cannot be combined with --format\n"}, {EXIT=>1}], @@ -748,6 +745,17 @@ my @Tests = ['fmt-15', '--format "--%100000f--" --to=si 4200', {OUT=>"--" . " " x 99996 . "4.2K--" }], + # --format padding overrides --padding + ['fmt-16', '--format="%6f" --padding=66 1234',{OUT=>" 1234"}], + + # zero padding + ['fmt-17', '--format="%06f" 1234',{OUT=>"001234"}], + # also support spaces (which are ignored as spacing is handled separately) + ['fmt-18', '--format="%0 6f" 1234',{OUT=>"001234"}], + # handle generic padding in combination + ['fmt-22', '--format="%06f" --padding=7 1234',{OUT=>" 001234"}], + ['fmt-23', '--format="%06f" --padding=-7 1234',{OUT=>"001234 "}], + ## Check all errors again, this time with --invalid=fail ## Input will be printed without conversion, @@ -881,6 +889,13 @@ my @Locale_Tests = ['lcl-fmt-4', '--format "--%-10f--" --to=si 5000000', {OUT=>"--5,0M --"}, {ENV=>"LC_ALL=$locale"}], + # handle zero/grouping in combination + ['lcl-fmt-5', '--format="%\'06f" 1234',{OUT=>"01 234"}, + {ENV=>"LC_ALL=$locale"}], + ['lcl-fmt-6', '--format="%0\'6f" 1234',{OUT=>"01 234"}, + {ENV=>"LC_ALL=$locale"}], + ['lcl-fmt-7', '--format="%0\'\'6f" 1234',{OUT=>"01 234"}, + {ENV=>"LC_ALL=$locale"}], ); if ($locale ne 'C') -- 1.7.7.6