build-aux/gen-lists-of-programs.sh | 1 + src/.gitignore | 1 + src/numfmt.c | 549 ++++++++++++++++++++++++++++++++++++ 3 files changed, 551 insertions(+), 0 deletions(-) diff --git a/build-aux/gen-lists-of-programs.sh b/build-aux/gen-lists-of-programs.sh index 212ce02..bf63ee3 100755 --- a/build-aux/gen-lists-of-programs.sh +++ b/build-aux/gen-lists-of-programs.sh @@ -85,6 +85,7 @@ normal_progs=' nl nproc nohup + numfmt od paste pathchk diff --git a/src/.gitignore b/src/.gitignore index 18cccc1..25573df 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -59,6 +59,7 @@ nice nl nohup nproc +numfmt od paste pathchk diff --git a/src/numfmt.c b/src/numfmt.c new file mode 100644 index 0000000..99b1450 --- /dev/null +++ b/src/numfmt.c @@ -0,0 +1,549 @@ +/* Reformat numbers like 11505426432 to the more human-readable 11G + Copyright (C) 2012 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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, see . */ + +#include +#include +#include +#include + +#include "argmatch.h" +#include "error.h" +#include "system.h" +#include "xstrtol.h" +#include "human.h" + +/* The official name of this program (e.g., no 'g' prefix). */ +#define PROGRAM_NAME "numfmt" + +#define AUTHORS proper_name ("XXXX") + +#define BUFFER_SIZE (16 * 1024) + +enum +{ + FROM_OPTION = CHAR_MAX + 1, + FROM_UNIT_OPTION, + TO_OPTION, + TO_UNIT_OPTION, + ROUND_OPTION, + SUFFIX_OPTION +}; + +enum scale_type +{ +scale_none, /* the default: no scaling */ +scale_auto, /* --from only */ +scale_SI, +scale_IEC, +scale_custom /* --to only, custom scale */ +}; + +static char const *const scale_from_args[] = +{ +"auto", "SI", "IEC", NULL +}; +static enum scale_type const scale_from_types[] = +{ +scale_auto, scale_SI, scale_IEC +}; + +static char const *const scale_to_args[] = +{ +"SI", "IEC", NULL +}; +static enum scale_type const scale_to_types[] = +{ +scale_SI, scale_IEC +}; + + +enum round_type +{ +round_ceiling, +round_floor, +round_nearest +}; + +static char const *const round_args[] = +{ +"ceiling","floor","nearest", NULL +}; + +static enum round_type const round_types[] = +{ +round_ceiling,round_floor,round_nearest +}; + +static struct option const longopts[] = +{ + {"from", required_argument, NULL, FROM_OPTION}, + {"from-unit", required_argument, NULL, FROM_UNIT_OPTION}, + {"to", required_argument, NULL, TO_OPTION}, + {"to-unit", required_argument, NULL, TO_UNIT_OPTION}, + {"round", required_argument, NULL, ROUND_OPTION}, + {"format", required_argument, NULL, 'f'}, + {"suffix", required_argument, NULL, SUFFIX_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + + +enum scale_type scale_from=scale_none; +enum scale_type scale_to=scale_none; +enum round_type _round=round_ceiling; +char const *format_str = NULL; +const char *suffix = NULL; +uintmax_t from_unit_size=1; +uintmax_t to_unit_size=1; +int human_print_options=0; /* see enum in 'human.c' */ + +/* + Converts a string to a long-long-int, optionally handling suffixes. + Mostly copied from /lib/xstdtol.c, with modified functionality. + */ + +/* xstrtoll.c and xstrtoull.c, which include this file, require that + ULLONG_MAX, LLONG_MAX, LLONG_MIN are defined, but does not + define them on all platforms. */ +#ifndef ULLONG_MAX +# define ULLONG_MAX TYPE_MAXIMUM (unsigned long long) +#endif +#ifndef LLONG_MAX +# define LLONG_MAX TYPE_MAXIMUM (long long int) +#endif +#ifndef LLONG_MIN +# define LLONG_MIN TYPE_MINIMUM (long long int) +#endif + +//The following #defines are copied from "xstrtoll.c" +#define STRTOL_T_MINIMUM LLONG_MIN +#define STRTOL_T_MAXIMUM LLONG_MAX +#undef __strtol_t +#define __strtol_t long long int +#define __strtol strtoll + +static strtol_error +bkm_scale (__strtol_t *x, int scale_factor) +{ + if (TYPE_SIGNED (__strtol_t) && *x < STRTOL_T_MINIMUM / scale_factor) + { + *x = STRTOL_T_MINIMUM; + return LONGINT_OVERFLOW; + } + if (STRTOL_T_MAXIMUM / scale_factor < *x) + { + *x = STRTOL_T_MAXIMUM; + return LONGINT_OVERFLOW; + } + *x *= scale_factor; + return LONGINT_OK; +} + +static strtol_error +bkm_scale_by_power (__strtol_t *x, int base, int power) +{ + strtol_error err = LONGINT_OK; + while (power--) + err |= bkm_scale (x, base); + return err; +} + +static strtol_error +human_xstrtol (const char *s, char **ptr, + __strtol_t *val, enum scale_type scaling) +{ + char *t_ptr; + char **p; + __strtol_t tmp; + strtol_error err = LONGINT_OK; + int base=1000; + int overflow=0; + + const int strtol_base = 10 ; //TODO: allow user-changable base? + + + p = (ptr ? ptr : &t_ptr); + + /* TODO: support negative numbers? */ +#if 0 + if (! TYPE_SIGNED (__strtol_t)) + { + const char *q = s; + unsigned char ch = *q; + while (isspace (ch)) + ch = *++q; + if (ch == '-') + return LONGINT_INVALID; + } +#endif + + errno = 0; + tmp = __strtol (s, p, strtol_base); + + if (*p == s) + { + /* No digits found */ + return LONGINT_INVALID; + } + else if (errno != 0) + { + if (errno != ERANGE) + return LONGINT_INVALID; + err = LONGINT_OVERFLOW; + } + + if (**p == '\0') + { + /* no suffixes after the number */ + *val = tmp; + return err; + } + + switch(scaling) + { + case scale_none: + /* Ignore any suffixes */ + *val = tmp; + return err; + + case scale_SI: + base = 1000 ; + break; + + case scale_IEC: + base = 1024; + break; + + case scale_auto: + base = (p[0][1]=='i') ? 1024 : 1000 ; + break; + + case scale_custom: + /* should never happen. assert? */ + error(EXIT_FAILURE,0,_("Internal error, scaling==scale_custom")); + } + + switch (**p) + { + case 'K': /* kilo/kibi */ + overflow = bkm_scale_by_power (&tmp, base, 1); + break; + + case 'M': /* mega or mebi */ + overflow = bkm_scale_by_power (&tmp, base, 2); + break; + + case 'G': /* giga or gibi */ + overflow = bkm_scale_by_power (&tmp, base, 3); + break; + + case 'T': /* tera or tebi */ + overflow = bkm_scale_by_power (&tmp, base, 4); + break; + + case 'P': /* peta or pebi */ + overflow = bkm_scale_by_power (&tmp, base, 5); + break; + + case 'E': /* exa or exbi */ + overflow = bkm_scale_by_power (&tmp, base, 6); + break; + + case 'Z': /* zetta or 2**70 */ + overflow = bkm_scale_by_power (&tmp, base, 7); + break; + + case 'Y': /* yotta or 2**80 */ + overflow = bkm_scale_by_power (&tmp, base, 8); + break; + + default: + *val = tmp; + return err | LONGINT_INVALID_SUFFIX_CHAR; + } + + err |= overflow; + + /* TODO: check for surplus suffix characters, and skip them? */ +#if 0 + *p += suffixes; + if (**p) + err |= LONGINT_INVALID_SUFFIX_CHAR; +#endif + + *val = tmp; + return err; +} + +static void +human_xstrtol_fatal (enum strtol_error err, + char const *input_str) +{ + char const *msgid; + + switch (err) + { + default: + abort (); + + case LONGINT_INVALID: + msgid = N_("invalid input number '%s'"); + break; + + case LONGINT_INVALID_SUFFIX_CHAR: + case LONGINT_INVALID_SUFFIX_CHAR_WITH_OVERFLOW: + msgid = N_("invalid suffix in input number '%s'"); + break; + + case LONGINT_OVERFLOW: + msgid = N_("input value too large '%s'"); + break; + } + error(EXIT_FAILURE,0,gettext(msgid), input_str); +} + +/* Convert a string of decimal digits, N_STRING, with an optional suffinx + to an integral value. Upon successful conversion, + return that value. If it cannot be converted, give a diagnostic and exit. +*/ +static uintmax_t +string_to_integer (const char *n_string) +{ + strtol_error s_err; + uintmax_t n; + + s_err = xstrtoumax (n_string, NULL, 10, &n, "bkKmMGTPEZY0"); + + if (s_err == LONGINT_OVERFLOW) + { + error (EXIT_FAILURE, 0, + _("%s: unit size is so large that it is not representable"), + n_string); + } + + if (s_err != LONGINT_OK) + { + error (EXIT_FAILURE, 0, _("%s: invalid unit size"), n_string); + } + return n; +} + + + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + emit_try_help (); + else + { + printf (_("\ +Usage: %s [OPTIONS] [NUMBER]\n\ +"), + program_name); + fputs (_("\ +Reformats NUMBER(s) to/from human-readable values.\n\ +Numbers can be processed either from stdin or command arguments.\n\ +\n\ +"), stdout); + fputs (_("\ + --from=UNIT Auto-scale input numbers (auto, SI, IEC)\n\ + If not specified, input suffixed are ignored.\n\ + --from-unit=N Specifiy the input unit size (instead of the default 1).\n\ + --to=UNIT Auto-scale output numbres (SI,IEC,).\n\ + If not specified, XXXX\n\ + --to-unit=N Specifiy the output unit size (instead of the default 1).\n\ + --rount=METHOD Round input numbers. METHOD can be:\n\ + ceiling (the default), floor, nearest\n\ + -f, --format=FORMAT use printf style output FORMAT.\n\ + Default output format is %d .\n\ + --suffix=SUFFIX XXXX\n\ + \n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + + fputs (_("\ +\n\ +UNIT options:\n\ + auto ('--from' only):\n\ + 1K = 1000\n\ + 1Ki = 1024\n\ + 1G = 1000000\n\ + 1Gi = 1048576\n\ + SI:\n\ + 1K* = 1000\n\ + (additional suffixes after K/G/T do not alter the scale)\n\ + IEC:\n\ + 1K* = 1024\n\ + (additional suffixes after K/G/T do not alter the scale)\n\ + ('--to' only):\n\ + Use number N as the scale.\n\ +\n\ +"), stdout); + + printf (_("\ +\n\ +Examples:\n\ + %s --to=SI 1000 -> \"1K\"\n\ + echo 1K | %s --from=SI -> \"1000\"\n\ + echo 1K | %s --from=IEC -> \"1024\"\n\ +"), + program_name, program_name, program_name); + emit_ancillary_info (); + } + exit (status); +} + +static void format_number(const char* str) +{ + char *ptr; + __strtol_t val=0; + strtol_error err = human_xstrtol(str,&ptr,&val,scale_from); + if (err != LONGINT_OK) + human_xstrtol_fatal (err, str); +#if 0 + printf("Parsing input = '%s' => %lld\n", str,val); +#endif + + char buf[LONGEST_HUMAN_READABLE + 1]; + fputs(human_readable(val, buf, human_print_options, + from_unit_size,to_unit_size),stdout); + if (suffix) + fputs(suffix,stdout); + fputs("\n",stdout); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while (true) + { + int c = getopt_long (argc, argv, "f:", longopts, NULL); + + if (c == -1) + break; + + switch (c) + { + case FROM_OPTION: + scale_from = XARGMATCH ("--from", optarg, scale_from_args, scale_from_types); + break; + + case FROM_UNIT_OPTION: + from_unit_size = string_to_integer(optarg); + break; + + case TO_OPTION: + //TODO: add custom handling for numeric/custom scale values + scale_to = XARGMATCH ("--to", optarg, scale_to_args, scale_to_types); + break; + + case TO_UNIT_OPTION: + to_unit_size = string_to_integer(optarg); + break; + + case ROUND_OPTION: + _round = XARGMATCH ("--round", optarg, round_args, round_types); + break; + + case 'f': + format_str = optarg; + break; + + case SUFFIX_OPTION: + suffix = optarg; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + switch(_round) + { + case round_ceiling: + human_print_options |= human_ceiling; + break; + case round_floor: + human_print_options |= human_floor; + break; + case round_nearest: + human_print_options |= human_round_to_nearest; + break; + } + + switch(scale_to) + { + case scale_SI: + human_print_options |= human_autoscale | human_SI; + break; + case scale_IEC: + human_print_options |= human_autoscale | human_base_1024 | human_SI ; + break; + case scale_custom: + /* TODO ?*/ + break; + case scale_none: + case scale_auto: + /* should never happen. assert? */ + break; + } + +#if 0 + printf("scale_from = %d\n", scale_from); + printf("scale_to = %d\n", scale_to); + printf("from_unit_size = %zu\n", from_unit_size); + printf("to_unit_size = %zu\n", to_unit_size); + printf("round = %d\n", _round); + printf("format = '%s'\n", format_str); + printf("suffix = '%s'\n", suffix); +#endif + + if (argc > optind) + { + for (; optind < argc; optind++) + format_number(argv[optind]); + } + else + { + char buf[BUFFER_SIZE + 1]; + + //TODO: allow multiple values on each line? + //TODO: support '--field=NUM' feature + while ( fgets(buf,BUFFER_SIZE,stdin) != NULL ) + format_number(buf); + + if (ferror(stdin)) + { + error(0,errno,_("error reading input")); + } + } + + exit (EXIT_SUCCESS); +}