diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index 5cf9d91f87..6c82534739 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -57,6 +57,12 @@ when using this option to properly escape. If this option is used at the command prompt then the % cannot be escaped and unintended expansion is possible. +To format numerical output for better human readability, append the `:pretty` +modifier to a numerical variable name (e.g., `--write-out '%{size_download:pretty}'`). +This modifier applies standard SI metric suffixes (K, M, G, etc.) and +constrains the output to a maximum width of 5 characters, including the +suffix. This works only with numerical variables. + The variables available are: ## `certs` diff --git a/src/tool_helpers.c b/src/tool_helpers.c index 1b998d2ca6..f28a66d929 100644 --- a/src/tool_helpers.c +++ b/src/tool_helpers.c @@ -121,3 +121,36 @@ void customrequest_helper(HttpReq req, const char *method) "the way you want. Consider using -I/--head instead."); } } + +char *max5data(curl_off_t bytes, char *max5, size_t mlen) +{ + /* a signed 64-bit value is 8192 petabytes maximum */ + const char unit[] = {'k', 'M', 'G', 'T', 'P', 'E', 0}; + int k = 0; + if(bytes < 100000) { + curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + return max5; + } + + do { + curl_off_t nbytes = bytes / 1024; + if(nbytes < 100) { + /* display with a decimal */ + curl_msnprintf(max5, mlen, + "%2" CURL_FORMAT_CURL_OFF_T ".%" CURL_FORMAT_CURL_OFF_T + "%c", + bytes / 1024, (bytes % 1024) * 10 / 1024, unit[k]); + break; + } + else if(nbytes < 10000) { + /* no decimals */ + curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, + unit[k]); + break; + } + bytes = nbytes; + k++; + DEBUGASSERT(unit[k]); + } while(unit[k]); + return max5; +} diff --git a/src/tool_helpers.h b/src/tool_helpers.h index cbfa168523..a812db2728 100644 --- a/src/tool_helpers.h +++ b/src/tool_helpers.h @@ -23,10 +23,20 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "tool_getparam.h" +#include "tool_sdecls.h" #include "tool_setup.h" const char *param2text(ParameterError error); int SetHTTPrequest(HttpReq req, HttpReq *store); void customrequest_helper(HttpReq req, const char *method); +/* The point of this function would be to return a string of the input data, + but never longer than 5 columns (+ one zero byte). + Add suffix k, M, G when suitable... + + Unit test @1622 +*/ +char *max5data(curl_off_t bytes, char *max5, size_t mlen); + #endif /* HEADER_CURL_TOOL_HELPERS_H */ diff --git a/src/tool_progress.c b/src/tool_progress.c index d3ee694fea..e5347af3dc 100644 --- a/src/tool_progress.c +++ b/src/tool_progress.c @@ -26,44 +26,6 @@ #include "tool_operate.h" #include "tool_progress.h" -/* The point of this function would be to return a string of the input data, - but never longer than 5 columns (+ one zero byte). - Add suffix k, M, G when suitable... - - Unit test 1622 - */ -UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen) -{ - /* a signed 64-bit value is 8192 petabytes maximum */ - static const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; - int k = 0; - if(bytes < 100000) { - curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); - return max5; - } - - do { - curl_off_t nbytes = bytes / 1024; - if(nbytes < 100) { - /* display with a decimal */ - curl_msnprintf(max5, mlen, "%2" CURL_FORMAT_CURL_OFF_T ".%" - CURL_FORMAT_CURL_OFF_T "%c", bytes / 1024, - (bytes % 1024) * 10 / 1024, unit[k]); - break; - } - else if(nbytes < 10000) { - /* no decimals */ - curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, - unit[k]); - break; - } - bytes = nbytes; - k++; - DEBUGASSERT(unit[k]); - } while(unit[k]); - return max5; -} - int xferinfo_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, diff --git a/src/tool_progress.h b/src/tool_progress.h index e07b5fd8d1..c6ea8d9efb 100644 --- a/src/tool_progress.h +++ b/src/tool_progress.h @@ -24,6 +24,7 @@ * ***************************************************************************/ #include "tool_setup.h" +#include "tool_helpers.h" int xferinfo_cb(void *clientp, curl_off_t dltotal, @@ -38,7 +39,6 @@ struct per_transfer; void progress_finalize(struct per_transfer *per); #ifdef UNITTESTS -UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen); UNITTEST void timebuf(char *r, size_t rlen, curl_off_t seconds); #endif diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 63e93ff52d..b21b8d898b 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -21,11 +21,17 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "curl_setup.h" +#include "curlx/dynbuf.h" +#include "tool_msgs.h" #include "tool_setup.h" #include "tool_cfgable.h" +#include "tool_helpers.h" #include "tool_writeout.h" #include "tool_writeout_json.h" +#include +#include struct httpmap { const char *str; @@ -43,8 +49,7 @@ static const struct httpmap http_version[] = { static int writeTime(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; curl_off_t us = 0; @@ -69,6 +74,13 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T ".%06" CURL_FORMAT_CURL_OFF_T, secs, us); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } } else { if(use_json) @@ -79,8 +91,7 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, } static int urlpart(struct per_transfer *per, writeoutid vid, - const char **contentp) -{ + const char **contentp) { CURLU *uh = curl_url(); int rc = 0; if(uh) { @@ -171,8 +182,7 @@ static void certinfo(struct per_transfer *per) static int writeString(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; const char *strinfo = NULL; const char *freestr = NULL; @@ -307,8 +317,16 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, "\"%s\":", wovar->name); jsonWriteString(stream, strinfo, FALSE); } - else + else { fputs(strinfo, stream); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } + } } else { if(use_json) @@ -322,8 +340,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, static int writeLong(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; long longinfo = 0; @@ -364,8 +381,16 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar, else { if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY) curl_mfprintf(stream, "%03ld", longinfo); - else + else { curl_mfprintf(stream, "%ld", longinfo); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } + } } } else { @@ -378,10 +403,10 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar, static int writeOffset(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; curl_off_t offinfo = 0; + char prettyOff[6]; (void)per; (void)per_result; @@ -408,7 +433,18 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, if(use_json) curl_mfprintf(stream, "\"%s\":", wovar->name); - curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); + switch(filter->id) { + case FILTER_NONE: + + curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); + break; + case FILTER_BYTES_PRETTY: + fputs(max5data(offinfo, prettyOff, sizeof(prettyOff)), stream); + break; + default: + curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T ":%s", offinfo, + filter->name); + } } else { if(use_json) @@ -759,18 +795,39 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, else { /* this is meant as a variable to output */ const char *end; - size_t vlen; + const char *filter; + size_t vlen, filen; if('{' == ptr[1]) { const struct writeoutvar *wv = NULL; - struct writeoutvar find = { 0 }; + struct writeoutfilter cur_fil; + struct writeoutvar find = {0}; + filter = strchr(ptr, ':'); end = strchr(ptr, '}'); ptr += 2; /* pass the % and the { */ if(!end) { fputs("%{", stream); continue; } - vlen = end - ptr; + if(filter && filter < end) { + vlen = filter - ptr; + filter += 1; /* remove the : */ + filen = end - filter; + + if(strncmp("pretty}", filter, filen) == 0) { + cur_fil.name = "pretty"; + cur_fil.id = FILTER_BYTES_PRETTY; + } + else { + errorf("Unknown --write-out filter: %s", filter); + break; + } + } + else { + vlen = end - ptr; + cur_fil.name = "none"; + cur_fil.id = FILTER_NONE; + } curlx_dyn_reset(&name); if(!curlx_dyn_addn(&name, ptr, vlen)) { find.name = curlx_dyn_ptr(&name); @@ -800,15 +857,15 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, stream = tool_stderr; break; case VAR_JSON: - ourWriteOutJSON(stream, variables, - CURL_ARRAYSIZE(variables), - per, per_result); + ourWriteOutJSON(stream, variables, CURL_ARRAYSIZE(variables), + per, per_result, &cur_fil); break; case VAR_HEADER_JSON: headerJSON(stream, per); break; default: - (void)wv->writefunc(stream, wv, per, per_result, FALSE); + (void)wv->writefunc(stream, wv, per, per_result, FALSE, + &cur_fil); break; } } diff --git a/src/tool_writeout.h b/src/tool_writeout.h index 7bc54aeb4d..3227330411 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -105,13 +105,23 @@ typedef enum { VAR_NUM_OF_VARS /* must be the last */ } writeoutid; +typedef enum { + FILTER_NONE, + FILTER_BYTES_PRETTY +} writeoutfilterid; + +struct writeoutfilter { + const char *name; + writeoutfilterid id; +}; + struct writeoutvar { const char *name; writeoutid id; CURLINFO ci; int (*writefunc)(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json); + bool use_json, const struct writeoutfilter *filter); }; void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, diff --git a/src/tool_writeout_json.c b/src/tool_writeout_json.c index 3b6f7df73f..500bde4b31 100644 --- a/src/tool_writeout_json.c +++ b/src/tool_writeout_json.c @@ -24,8 +24,8 @@ #include "tool_setup.h" #include "tool_cfgable.h" -#include "tool_writeout_json.h" #include "tool_writeout.h" +#include "tool_writeout_json.h" #define MAX_JSON_STRING 100000 @@ -95,8 +95,8 @@ void jsonWriteString(FILE *stream, const char *in, bool lowercase) } void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], - size_t nentries, - struct per_transfer *per, CURLcode per_result) + size_t nentries, struct per_transfer *per, + CURLcode per_result, const struct writeoutfilter *filter) { size_t i; @@ -104,7 +104,8 @@ void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], for(i = 0; i < nentries; i++) { if(mappings[i].writefunc && - mappings[i].writefunc(stream, &mappings[i], per, per_result, TRUE)) + mappings[i].writefunc(stream, &mappings[i], per, per_result, TRUE, + filter)) fputs(",", stream); } diff --git a/src/tool_writeout_json.h b/src/tool_writeout_json.h index 7318367c17..e1bc00f405 100644 --- a/src/tool_writeout_json.h +++ b/src/tool_writeout_json.h @@ -30,8 +30,8 @@ int jsonquoted(const char *in, size_t len, struct dynbuf *out, bool lowercase); void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], - size_t nentries, - struct per_transfer *per, CURLcode per_result); + size_t nentries, struct per_transfer *per, + CURLcode per_result, const struct writeoutfilter *filter); void headerJSON(FILE *stream, struct per_transfer *per); void jsonWriteString(FILE *stream, const char *in, bool lowercase); diff --git a/tests/tunit/tool1622.c b/tests/tunit/tool1622.c index 6844cf0ec5..44559c2bf8 100644 --- a/tests/tunit/tool1622.c +++ b/tests/tunit/tool1622.c @@ -23,6 +23,7 @@ ***************************************************************************/ #include "unitcheck.h" #include "tool_progress.h" +#include "tool_helpers.h" static CURLcode test_tool1622(const char *arg) {