From bab8cc2a6bcf2f373365df084565fb7627095f60 Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Fri, 28 Aug 2020 22:20:53 +0100 Subject: [PATCH] mosquitto_sub now supports extra format specifiers. These are for field width and precision for some parameters. --- ChangeLog.txt | 2 + client/client_shared.c | 39 +++ client/sub_client_output.c | 479 +++++++++++++++++++++++------------- client/sub_test_fixed_width | 59 +++++ man/Makefile | 32 +-- man/mosquitto_sub.1.xml | 54 +++- 6 files changed, 481 insertions(+), 184 deletions(-) create mode 100755 client/sub_test_fixed_width diff --git a/ChangeLog.txt b/ChangeLog.txt index fd643a8b..4021e1bc 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -56,6 +56,8 @@ Clients: of received messages to be printed. - mosquitto_sub %j and %J timestamps are now in a ISO 8601 compatible format. - mosquitto_pub now sends 0 length files without an error when using `-f`. +- mosquitto_sub now supports extra format specifiers for field width and + precision for some parameters. 1.6.9 - 20200227 diff --git a/client/client_shared.c b/client/client_shared.c index 8f78bfb7..f62f0bab 100644 --- a/client/client_shared.c +++ b/client/client_shared.c @@ -55,6 +55,45 @@ static int check_format(const char *str) fprintf(stderr, "Error: Incomplete format specifier.\n"); return 1; }else{ + if(str[i+1] == '0' || str[i+1] == '-'){ + /* Flag characters */ + i++; + if(i == len-1){ + // error + fprintf(stderr, "Error: Incomplete format specifier.\n"); + return 1; + } + } + + /* Field width */ + while(str[i+1] >= '0' && str[i+1] <= '9'){ + i++; + if(i == len-1){ + // error + fprintf(stderr, "Error: Incomplete format specifier.\n"); + return 1; + } + } + + if(str[i+1] == '.'){ + /* Precision specifier */ + i++; + if(i == len-1){ + // error + fprintf(stderr, "Error: Incomplete format specifier.\n"); + return 1; + } + /* Precision */ + while(str[i+1] >= '0' && str[i+1] <= '9'){ + i++; + if(i == len-1){ + // error + fprintf(stderr, "Error: Incomplete format specifier.\n"); + return 1; + } + } + } + if(str[i+1] == '%'){ // Print %, ignore }else if(str[i+1] == 'A'){ diff --git a/client/sub_client_output.c b/client/sub_client_output.c index 9bef4c9b..06dc7fe9 100644 --- a/client/sub_client_output.c +++ b/client/sub_client_output.c @@ -84,9 +84,30 @@ static int get_time(struct tm **ti, long *ns) } -static void write_payload(const unsigned char *payload, int payloadlen, int hex) +static void write_payload(const unsigned char *payload, int payloadlen, int hex, char align, char pad, int field_width, int precision) { int i; + int padlen; + + if(field_width > 0){ + if(payloadlen > field_width){ + payloadlen = field_width; + } + if(hex > 0){ + payloadlen /= 2; + padlen = field_width - payloadlen*2; + }else{ + padlen = field_width - payloadlen; + } + }else{ + padlen = field_width - payloadlen; + } + + if(align != '-'){ + for(i=0; ipayload, message->payloadlen, 0); + write_payload(message->payload, message->payloadlen, 0, 0, 0, 0); fputs("}", stdout); } @@ -334,6 +359,248 @@ static int json_print(const struct mosquitto_message *message, const mosquitto_p } +static void formatted_print_blank(char pad, int field_width) +{ + int i; + for(i=0; ipretty) != MOSQ_ERR_SUCCESS){ + err_printf(lcfg, "Error: Out of memory.\n"); + return; + } + break; + + case 'J': + if(!ti){ + if(get_time(&ti, &ns)){ + err_printf(lcfg, "Error obtaining system time.\n"); + return; + } + } + rc = json_print(message, properties, ti, ns, false, lcfg->pretty); + if(rc == MOSQ_ERR_NOMEM){ + err_printf(lcfg, "Error: Out of memory.\n"); + return; + }else if(rc == MOSQ_ERR_INVAL){ + err_printf(lcfg, "Error: Message payload is not valid JSON on topic %s.\n", message->topic); + return; + } + break; + + case 'l': + formatted_print_int(message->payloadlen, align, pad, field_width); + break; + + case 'm': + formatted_print_int(message->mid, align, pad, field_width); + break; + + case 'P': + strname = NULL; + strvalue = NULL; + prop = mosquitto_property_read_string_pair(properties, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, false); + while(prop){ + printf("%s:%s", strname, strvalue); + free(strname); + free(strvalue); + strname = NULL; + strvalue = NULL; + + prop = mosquitto_property_read_string_pair(prop, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, true); + if(prop){ + fputc(' ', stdout); + } + } + free(strname); + free(strvalue); + break; + + case 'p': + write_payload(message->payload, message->payloadlen, 0, align, pad, field_width, precision); + break; + + case 'q': + fputc(message->qos + 48, stdout); + break; + + case 'R': + if(mosquitto_property_read_string(properties, MQTT_PROP_RESPONSE_TOPIC, &strvalue, false)){ + formatted_print_str(strvalue, align, field_width, precision); + free(strvalue); + } + break; + + case 'r': + if(message->retain){ + fputc('1', stdout); + }else{ + fputc('0', stdout); + } + break; + + case 'S': + if(mosquitto_property_read_varint(properties, MQTT_PROP_SUBSCRIPTION_IDENTIFIER, &i32value, false)){ + formatted_print_int(i32value, align, pad, field_width); + }else{ + formatted_print_blank(pad, field_width); + } + break; + + case 't': + formatted_print_str(message->topic, align, field_width, precision); + break; + + case 'U': + if(!ti){ + if(get_time(&ti, &ns)){ + err_printf(lcfg, "Error obtaining system time.\n"); + return; + } + } + if(strftime(buf, 100, "%s", ti) != 0){ + printf("%s.%09ld", buf, ns); + } + break; + + case 'x': + write_payload(message->payload, message->payloadlen, 1, align, pad, field_width, precision); + break; + + case 'X': + write_payload(message->payload, message->payloadlen, 2, align, pad, field_width, precision); + break; + } +} + + static void formatted_print(const struct mosq_config *lcfg, const struct mosquitto_message *message, const mosquitto_property *properties) { int len; @@ -342,178 +609,56 @@ static void formatted_print(const struct mosq_config *lcfg, const struct mosquit long ns; char strf[3]; char buf[100]; - int rc; - uint8_t i8value; - uint16_t i16value; - uint32_t i32value; - char *binvalue, *strname, *strvalue; - const mosquitto_property *prop; + char align, pad; + int field_width, precision; len = strlen(lcfg->format); for(i=0; iformat[i] == '%'){ + align = 0; + pad = ' '; + field_width = 0; + precision = -1; if(i < len-1){ i++; - switch(lcfg->format[i]){ - case '%': - fputc('%', stdout); - break; - - case 'A': - if(mosquitto_property_read_int16(properties, MQTT_PROP_TOPIC_ALIAS, &i16value, false)){ - printf("%d", i16value); + /* Optional alignment */ + if(lcfg->format[i] == '-'){ + align = lcfg->format[i]; + if(i < len-1){ + i++; + } + } + /* "%-040p" is allowed by this combination of checks, but isn't + * a valid format specifier, the '0' will be ignored. */ + /* Optional zero padding */ + if(lcfg->format[i] == '0'){ + pad = '0'; + if(i < len-1){ + i++; + } + } + /* Optional field width */ + while(i < len-1 && lcfg->format[i] >= '0' && lcfg->format[i] <= '9'){ + field_width *= 10; + field_width += lcfg->format[i]-'0'; + i++; + } + /* Optional precision */ + if(lcfg->format[i] == '.'){ + if(i < len-1){ + i++; + precision = 0; + while(i < len-1 && lcfg->format[i] >= '0' && lcfg->format[i] <= '9'){ + precision *= 10; + precision += lcfg->format[i]-'0'; + i++; } - break; + } + } - case 'C': - if(mosquitto_property_read_string(properties, MQTT_PROP_CONTENT_TYPE, &strvalue, false)){ - printf("%s", strvalue); - free(strvalue); - } - break; - - case 'D': - if(mosquitto_property_read_binary(properties, MQTT_PROP_CORRELATION_DATA, (void **)&binvalue, &i16value, false)){ - fwrite(binvalue, 1, i16value, stdout); - free(binvalue); - } - break; - - case 'E': - if(mosquitto_property_read_int32(properties, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, &i32value, false)){ - printf("%d", i32value); - } - break; - - case 'F': - if(mosquitto_property_read_byte(properties, MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, &i8value, false)){ - printf("%d", i8value); - } - break; - - case 'I': - if(!ti){ - if(get_time(&ti, &ns)){ - err_printf(lcfg, "Error obtaining system time.\n"); - return; - } - } - if(strftime(buf, 100, "%FT%T%z", ti) != 0){ - fputs(buf, stdout); - } - break; - - case 'j': - if(!ti){ - if(get_time(&ti, &ns)){ - err_printf(lcfg, "Error obtaining system time.\n"); - return; - } - } - if(json_print(message, properties, ti, ns, true, lcfg->pretty) != MOSQ_ERR_SUCCESS){ - err_printf(lcfg, "Error: Out of memory.\n"); - return; - } - break; - - case 'J': - if(!ti){ - if(get_time(&ti, &ns)){ - err_printf(lcfg, "Error obtaining system time.\n"); - return; - } - } - rc = json_print(message, properties, ti, ns, false, lcfg->pretty); - if(rc == MOSQ_ERR_NOMEM){ - err_printf(lcfg, "Error: Out of memory.\n"); - return; - }else if(rc == MOSQ_ERR_INVAL){ - err_printf(lcfg, "Error: Message payload is not valid JSON on topic %s.\n", message->topic); - return; - } - break; - - case 'l': - printf("%d", message->payloadlen); - break; - - case 'm': - printf("%d", message->mid); - break; - - case 'P': - strname = NULL; - strvalue = NULL; - prop = mosquitto_property_read_string_pair(properties, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, false); - while(prop){ - printf("%s:%s", strname, strvalue); - free(strname); - free(strvalue); - strname = NULL; - strvalue = NULL; - - prop = mosquitto_property_read_string_pair(prop, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, true); - if(prop){ - fputc(' ', stdout); - } - } - free(strname); - free(strvalue); - break; - - case 'p': - write_payload(message->payload, message->payloadlen, 0); - break; - - case 'q': - fputc(message->qos + 48, stdout); - break; - - case 'R': - if(mosquitto_property_read_string(properties, MQTT_PROP_RESPONSE_TOPIC, &strvalue, false)){ - printf("%s", strvalue); - free(strvalue); - } - break; - - case 'r': - if(message->retain){ - fputc('1', stdout); - }else{ - fputc('0', stdout); - } - break; - - case 'S': - if(mosquitto_property_read_varint(properties, MQTT_PROP_SUBSCRIPTION_IDENTIFIER, &i32value, false)){ - printf("%d", i32value); - } - break; - - case 't': - fputs(message->topic, stdout); - break; - - case 'U': - if(!ti){ - if(get_time(&ti, &ns)){ - err_printf(lcfg, "Error obtaining system time.\n"); - return; - } - } - if(strftime(buf, 100, "%s", ti) != 0){ - printf("%s.%09ld", buf, ns); - } - break; - - case 'x': - write_payload(message->payload, message->payloadlen, 1); - break; - - case 'X': - write_payload(message->payload, message->payloadlen, 2); - break; + if(i < len){ + formatted_print_percent(lcfg, message, properties, lcfg->format[i], align, pad, field_width, precision); } } }else if(lcfg->format[i] == '@'){ @@ -616,7 +761,7 @@ void print_message(struct mosq_config *cfg, const struct mosquitto_message *mess }else if(cfg->verbose){ if(message->payloadlen){ printf("%s ", message->topic); - write_payload(message->payload, message->payloadlen, false); + write_payload(message->payload, message->payloadlen, false, 0, 0, 0, 0); if(cfg->eol){ printf("\n"); } @@ -628,7 +773,7 @@ void print_message(struct mosq_config *cfg, const struct mosquitto_message *mess fflush(stdout); }else{ if(message->payloadlen){ - write_payload(message->payload, message->payloadlen, false); + write_payload(message->payload, message->payloadlen, false, 0, 0, 0, 0); if(cfg->eol){ printf("\n"); } diff --git a/client/sub_test_fixed_width b/client/sub_test_fixed_width new file mode 100755 index 00000000..c3b77fe5 --- /dev/null +++ b/client/sub_test_fixed_width @@ -0,0 +1,59 @@ +LD_LIBRARY_PATH=../lib ./mosquitto_sub \ + -h test.mosquitto.org \ + --retained-only \ + --remove-retained \ + -t FW/# \ + -W 1>/dev/null 2>/dev/null + +LD_LIBRARY_PATH=../lib ./mosquitto_pub \ + -h test.mosquitto.org \ + -D publish content-type "application/json" \ + -D publish message-expiry-interval 360000 \ + -D publish payload-format-indicator 1 \ + -D publish response-topic response-topic \ + -m ABCDEFGHIJKLMNOPQRSTUVWXYZ \ + -q 2 \ + -r \ + -t FW/truncate \ + -V 5 + +LD_LIBRARY_PATH=../lib ./mosquitto_pub \ + -h test.mosquitto.org \ + -D publish content-type "null" \ + -D publish message-expiry-interval 3600 \ + -D publish payload-format-indicator 1 \ + -D publish response-topic r-t \ + -m Off \ + -q 2 \ + -r \ + -t FW/expire \ + -V 5 + +LD_LIBRARY_PATH=../lib ./mosquitto_pub \ + -h test.mosquitto.org \ + -D publish payload-format-indicator 1 \ + -D publish response-topic rt \ + -m Offline \ + -q 2 \ + -r \ + -t FW/1 \ + -V 5 + +LD_LIBRARY_PATH=../lib ./mosquitto_sub \ + -h test.mosquitto.org \ + -C 3 \ + -F "| %10t | %-10t | %7x | %-7x | %08x | %7X | %-7X | %08X | %8p | %-8p | %3m | %-3m | %03m | %3l | %-3l | %03l | %2F | %-2F | %02F | %5C | %-5C | %5E | %-5E | %05E | %5A | %5R | %-5R |" \ + -q 2 \ + -t 'FW/#' \ + -V 5 + +echo + +LD_LIBRARY_PATH=../lib ./mosquitto_sub \ + -h test.mosquitto.org \ + -C 3 \ + -F "| %10.10t | %.5t | %-10.10t | %7x | %-7x | %08x | %7X | %-7X | %08X | %8p | %-8p | %3m | %-3m | %03m | %3l | %-3l | %03l | %2F | %-2F | %02F | %5C | %-5C | %5E | %-5E | %05E | %5.5A | %5.5R | %-5.5R |" \ + -q 2 \ + -t 'FW/#' \ + -V 5 + diff --git a/man/Makefile b/man/Makefile index f7268cca..94aff2c5 100644 --- a/man/Makefile +++ b/man/Makefile @@ -50,32 +50,32 @@ uninstall : -rm -f "${DESTDIR}${mandir}/man7/mosquitto-tls.7" -rm -f "${DESTDIR}${mandir}/man3/libmosquitto.3" -mosquitto.8 : mosquitto.8.xml - $(XSLTPROC) $^ +mosquitto.8 : mosquitto.8.xml manpage.xsl + $(XSLTPROC) $< mosquitto.conf.5 : mosquitto.conf.5.xml manpage.xsl $(XSLTPROC) $< -mosquitto_passwd.1 : mosquitto_passwd.1.xml - $(XSLTPROC) $^ +mosquitto_passwd.1 : mosquitto_passwd.1.xml manpage.xsl + $(XSLTPROC) $< -mosquitto_pub.1 : mosquitto_pub.1.xml - $(XSLTPROC) $^ +mosquitto_pub.1 : mosquitto_pub.1.xml manpage.xsl + $(XSLTPROC) $< -mosquitto_sub.1 : mosquitto_sub.1.xml - $(XSLTPROC) $^ +mosquitto_sub.1 : mosquitto_sub.1.xml manpage.xsl + $(XSLTPROC) $< -mosquitto_rr.1 : mosquitto_rr.1.xml - $(XSLTPROC) $^ +mosquitto_rr.1 : mosquitto_rr.1.xml manpage.xsl + $(XSLTPROC) $< -mqtt.7 : mqtt.7.xml - $(XSLTPROC) $^ +mqtt.7 : mqtt.7.xml manpage.xsl + $(XSLTPROC) $< -mosquitto-tls.7 : mosquitto-tls.7.xml - $(XSLTPROC) $^ +mosquitto-tls.7 : mosquitto-tls.7.xml manpage.xsl + $(XSLTPROC) $< -libmosquitto.3 : libmosquitto.3.xml - $(XSLTPROC) $^ +libmosquitto.3 : libmosquitto.3.xml manpage.xsl + $(XSLTPROC) $< html : *.xml set -e; for m in *.xml; \ diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index 8419197b..813d9f5e 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -790,7 +790,7 @@ mosquitto_sub -t 'bbc/#' -T bbc/bbc1 --remove-retained - Output format + Output Format There are three ways of formatting the output from mosquitto_sub. In all cases a new-line character is appended for each message received unless the argument is passed to @@ -824,6 +824,58 @@ mosquitto_sub -t 'bbc/#' -T bbc/bbc1 --remove-retained character is , which is used to input some characters that would otherwise be difficult to enter. + + Flag characters + The parameters %A, %C, %E, %F, %I, %l, %m, %p, %R, %S, %t, %x, and %X can have optional flags immediately after the % character. + + + + The value should be zero padded. + This applies to the parameters %A, %E, %F, %l, %m, %S, %X, and %x. + It will be ignored for other parameters. If used with the + flag, the flag will be + ignored. + + + + + The value will be left aligned to the field width, + padded with blanks. The default is right alignment, with either 0 + or blank padding. + + + + + + Field width + + Some of the MQTT related parameters can be formatted with an + option to set their field width in a similar way to regular + printf style formats, i.e. this sets the minimum width when + printing this parameter. This applies to the options %A, %C, + %E, %F, %I, %l, %m, %p, %R, %S, %t, %x, %X. + + + For example would set the minimum topic + field width to 10 characters. + + + + + Maximum width + + Some of the MQTT related parameters can be formatted with an + option to set a maximum field width in a similar way to regular + printf style formats. This applies to the options %C, %I, %R, %t. + + + For example would set the minimum topic + field width to 10 characters, and the maximum topic width to + 10 characters, i.e. the field will always be exactly 10 + characters long. + + + MQTT related parameters