From 36df78dd93a7495f278f9ed0a5ef67924cdd7adf Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 15:53:56 +0100 Subject: [PATCH 01/34] lib/compat/humanize_number.c: switch to freebsd version --- include/compat.h | 20 +++-- lib/compat/humanize_number.c | 138 ++++++++++++++++++++++------------- 2 files changed, 101 insertions(+), 57 deletions(-) diff --git a/include/compat.h b/include/compat.h index cedd1be29..f33731ab0 100644 --- a/include/compat.h +++ b/include/compat.h @@ -28,13 +28,19 @@ int HIDDEN vasprintf(char **, const char *, va_list); #endif #ifndef HAVE_HUMANIZE_NUMBER -#define HN_DECIMAL 0x01 -#define HN_NOSPACE 0x02 -#define HN_B 0x04 -#define HN_DIVISOR_1000 0x08 -#define HN_GETSCALE 0x10 -#define HN_AUTOSCALE 0x20 -int HIDDEN humanize_number(char *, size_t, int64_t, const char *, int, int); +/* Values for humanize_number(3)'s flags parameter. */ +#define HN_DECIMAL 0x01 +#define HN_NOSPACE 0x02 +#define HN_B 0x04 +#define HN_DIVISOR_1000 0x08 +#define HN_IEC_PREFIXES 0x10 + +/* Values for humanize_number(3)'s scale parameter. */ +#define HN_GETSCALE 0x10 +#define HN_AUTOSCALE 0x20 + +int HIDDEN humanize_number(char *_buf, size_t _len, int64_t _number, + const char *_suffix, int _scale, int _flags); #endif #endif /* COMPAT_H */ diff --git a/lib/compat/humanize_number.c b/lib/compat/humanize_number.c index 446c94b83..c7c4bb336 100644 --- a/lib/compat/humanize_number.c +++ b/lib/compat/humanize_number.c @@ -1,7 +1,10 @@ /* $NetBSD: humanize_number.c,v 1.14 2008/04/28 20:22:59 martin Exp $ */ -/* +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc. + * Copyright 2013 John-Mark Gurney * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -30,6 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -37,61 +41,85 @@ #include #include -#include "xbps_api_impl.h" #include "compat.h" + +static const int maxscale = 6; + + int HIDDEN -humanize_number(char *buf, size_t len, int64_t bytes, - const char *suffix, int scale, int flags) +humanize_number(char *buf, size_t len, int64_t quotient, + const char *suffix, int scale, int flags) { const char *prefixes, *sep; - int b, i, r, maxscale, s1, s2, sign; + int i, r, remainder, s1, s2, sign; + int divisordeccut; int64_t divisor, max; size_t baselen; - assert(buf != NULL); - assert(suffix != NULL); - assert(scale >= 0); + /* Since so many callers don't check -1, NUL terminate the buffer */ + if (len > 0) + buf[0] = '\0'; - if (flags & HN_DIVISOR_1000) { - /* SI for decimal multiplies */ - divisor = 1000; - if (flags & HN_B) - prefixes = "B\0k\0M\0G\0T\0P\0E"; - else - prefixes = "\0\0k\0M\0G\0T\0P\0E"; - } else { + /* validate args */ + if (buf == NULL || suffix == NULL) + return (-1); + if (scale < 0) + return (-1); + else if (scale > maxscale && + ((scale & ~(HN_AUTOSCALE|HN_GETSCALE)) != 0)) + return (-1); + if ((flags & HN_DIVISOR_1000) && (flags & HN_IEC_PREFIXES)) + return (-1); + + /* setup parameters */ + remainder = 0; + + if (flags & HN_IEC_PREFIXES) { + baselen = 2; /* - * binary multiplies - * XXX IEC 60027-2 recommends Ki, Mi, Gi... + * Use the prefixes for power of two recommended by + * the International Electrotechnical Commission + * (IEC) in IEC 80000-3 (i.e. Ki, Mi, Gi...). + * + * HN_IEC_PREFIXES implies a divisor of 1024 here + * (use of HN_DIVISOR_1000 would have triggered + * an assertion earlier). */ divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ if (flags & HN_B) - prefixes = "B\0K\0M\0G\0T\0P\0E"; + prefixes = "B\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; else - prefixes = "\0\0K\0M\0G\0T\0P\0E"; + prefixes = "\0\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; + } else { + baselen = 1; + if (flags & HN_DIVISOR_1000) { + divisor = 1000; + divisordeccut = 950; + if (flags & HN_B) + prefixes = "B\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + } else { + divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ + if (flags & HN_B) + prefixes = "B\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + } } -#define SCALE2PREFIX(scale) (&prefixes[(scale) << 1]) - maxscale = 7; +#define SCALE2PREFIX(scale) (&prefixes[(scale) * 3]) - if (scale >= maxscale && - (scale & (HN_AUTOSCALE | HN_GETSCALE)) == 0) - return (-1); - - if (buf == NULL || suffix == NULL) - return (-1); - - if (len > 0) - buf[0] = '\0'; - if (bytes < 0) { + if (quotient < 0) { sign = -1; - bytes *= -100; - baselen = 3; /* sign, digit, prefix */ + quotient = -quotient; + baselen += 2; /* sign, digit */ } else { sign = 1; - bytes *= 100; - baselen = 2; /* digit, prefix */ + baselen += 1; /* digit */ } if (flags & HN_NOSPACE) sep = ""; @@ -107,7 +135,7 @@ humanize_number(char *buf, size_t len, int64_t bytes, if (scale & (HN_AUTOSCALE | HN_GETSCALE)) { /* See if there is additional columns can be used. */ - for (max = 100, i = (int)(len - baselen); i-- > 0;) + for (max = 1, i = len - baselen; i-- > 0;) max *= 10; /* @@ -115,29 +143,39 @@ humanize_number(char *buf, size_t len, int64_t bytes, * If there will be an overflow by the rounding below, * divide once more. */ - for (i = 0; bytes >= max - 50 && i < maxscale; i++) - bytes /= divisor; + for (i = 0; + (quotient >= max || (quotient == max - 1 && + (remainder >= divisordeccut || remainder >= + divisor / 2))) && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } if (scale & HN_GETSCALE) return (i); - } else - for (i = 0; i < scale && i < maxscale; i++) - bytes /= divisor; + } else { + for (i = 0; i < scale && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } + } /* If a value <= 9.9 after rounding and ... */ - if (bytes < 995 && i > 0 && flags & HN_DECIMAL) { - /* baselen + \0 + .N */ - if (len < baselen + 1 + 2) - return (-1); - b = ((int)bytes + 5) / 10; - s1 = b / 10; - s2 = b % 10; + /* + * XXX - should we make sure there is enough space for the decimal + * place and if not, don't do HN_DECIMAL? + */ + if (((quotient == 9 && remainder < divisordeccut) || quotient < 9) && + i > 0 && flags & HN_DECIMAL) { + s1 = (int)quotient + ((remainder * 10 + divisor / 2) / + divisor / 10); + s2 = ((remainder * 10 + divisor / 2) / divisor) % 10; r = snprintf(buf, len, "%d%s%d%s%s%s", sign * s1, localeconv()->decimal_point, s2, sep, SCALE2PREFIX(i), suffix); } else r = snprintf(buf, len, "%" PRId64 "%s%s%s", - sign * ((bytes + 50) / 100), + sign * (quotient + (remainder + divisor / 2) / divisor), sep, SCALE2PREFIX(i), suffix); return (r); From ca409619e2df0b6943bb520fd590004d2bbfd404 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 20 Feb 2023 23:53:18 +0100 Subject: [PATCH 02/34] lib: add xbps_fmt* functions for string formatting --- include/xbps.h.in | 192 ++++++++++++++++++ lib/Makefile | 2 +- lib/format.c | 504 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 lib/format.c diff --git a/include/xbps.h.in b/include/xbps.h.in index 5ccd51e94..134da0e41 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2370,6 +2370,198 @@ xbps_plist_dictionary_from_file(const char *path); /**@}*/ +/** @addtogroup format */ +/**@{*/ + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format string. + */ +struct xbps_fmt; + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_spec { + /** + * @var conversion + * @brief Output conversion. + */ + char conversion; + /** + * @var fill + * @brief Padding character. + */ + char fill; + /** + * @var align + * @brief Alignment modifier. + * + * Possible values are: + * - `<`: left align. + * - `>`: right align. + * - `=`: place padding after the sign. + */ + char align; + /** + * @var sign + * @brief Sign modifier. + * + * Possible values are: + * - `-`: sign negative numbers. + * - `+`: sign both negative and positive numbers. + * - space: sign negative numbers and add space before positive numbers. + */ + char sign; + /** + * @var width + * @brief Minimum width. + */ + unsigned int width; + /** + * @var precision + * @brief Precision. + */ + unsigned int precision; + /** + * @var type + * @brief Type specifier usually to change the output format type. + * + * Can contain any character, xbps_fmt_number() uses the following: + * - `u`: Unsigned decimal. + * - `d`: Decimal. + * - `x`: Hex with lowercase letters. + * - `X`: hex with uppercase letters. + * - `h`: Human readable using humanize_number(3). + */ + char type; +}; + +/** + * @brief Format callback, called for each variable in the format string. + * + * A callback function should write data associated with \a var to \a fp and use + * \a w as alignment specifier. + * + * @param[in] fp The file to print to. + * @param[in] spec The format specifier. + * @param[in] var The format string variable name. + * @param[in] data Userdata passed to the xbps_fmt() function. + */ +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data); + +/** + * @brief Parses the format string \a format. + * + * @param[in] format The format string. + * + * @return The parsed format structure, or NULL on error. + * The returned buffer must be freed with xbps_fmt_free(). + * @retval EINVAL Invalid format string. + * @retval ERANGE Invalid alignment specifier. + * @retval ENOMEM Memory allocation failure. + */ +struct xbps_fmt *xbps_fmt_parse(const char *format); + +/** + * @brief Releases memory associated with \a fmt. + * + * @param[in] fmt The format string. + */ +void xbps_fmt_free(struct xbps_fmt *fmt); + +/** + * @brief Print formatted text to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success or a negative errno. + * @retval 0 Success + */ +int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the parsed \a fmt + * format string to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the format string + * \a format to \a fp. + * + * @param[in] format Format string. + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary to \a fp. + * + * Print the formatted dictionary according to the \a format format string + * to \a fp. + * + * @param[in] format Format string. + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success. + * @retval 0 Success. + * @retval -EINVAL Invalid format string. + * @retval -ERANGE Invalid alignment specifier. + * @retval -ENOMEM Memory allocation failure. + */ +int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted number to \a fp. + * + * Prints the number \d to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] num Number to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); + +/** + * @brief Print formatted string to \a fp. + * + * Prints the string \a str to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] str String to print. + * @param[in] len Length of the string or 0. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp); + +/**@}*/ + #ifdef __cplusplus } #endif diff --git a/lib/Makefile b/lib/Makefile index 0cf6ac84f..bd9838570 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -51,7 +51,7 @@ OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o OBJS += repo.o repo_sync.o OBJS += rpool.o cb_util.o proplib_wrapper.o OBJS += package_alternatives.o -OBJS += conf.o log.o +OBJS += conf.o log.o format.o OBJS += $(EXTOBJS) $(COMPAT_OBJS) # unnecessary unless pkgdb format changes # OBJS += pkgdb_conversion.o diff --git a/lib/format.c b/lib/format.c new file mode 100644 index 000000000..80f8902cb --- /dev/null +++ b/lib/format.c @@ -0,0 +1,504 @@ +/*- + * Copyright (c) 2023 Duncan Overbruck . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "xbps_api_impl.h" +#include "compat.h" + +/** + * @file lib/format.c + * @brief Format printing functions + * @defgroup format Format printing fuctions + * + * The format strings are similar to normal printf() format strings, + * but instead of character to specify types variable names are used. + * + */ + +struct strbuf { + size_t sz, len; + char *mem; +}; + +static int +strbuf_grow(struct strbuf *sb, size_t n) +{ + char *tmp; + size_t nsz; + if (sb->len+n+1 < sb->sz) + return 0; + nsz = 2*sb->sz + 16; + tmp = realloc(sb->mem, nsz); + if (!tmp) + return -errno; + sb->mem = tmp; + sb->sz = nsz; + return 0; +} + +static int +strbuf_putc(struct strbuf *sb, char c) +{ + int r = strbuf_grow(sb, 1); + if (r < 0) + return 0; + sb->mem[sb->len++] = c; + sb->mem[sb->len] = '\0'; + return 0; +} + +static int +strbuf_puts(struct strbuf *sb, const char *s, size_t n) +{ + int r = strbuf_grow(sb, n); + if (r < 0) + return 0; + memcpy(sb->mem+sb->len, s, n); + sb->len += n; + sb->mem[sb->len] = '\0'; + return 0; +} + +static void +strbuf_reset(struct strbuf *sb) +{ + sb->len = 0; + if (sb->mem) + sb->mem[0] = '\0'; +} + +static void +strbuf_release(struct strbuf *sb) +{ + free(sb->mem); + sb->mem = NULL; + sb->len = sb->sz = 0; +} + +struct common { + int type; +}; + +struct chunk { + struct common common; + char s[]; +}; + +struct var { + struct common common; + struct xbps_fmt_spec spec; + char s[]; +}; + +struct xbps_fmt { + union { + struct common *common; + struct chunk *chunk; + struct var *var; + }; +}; + +enum { + TTEXT = 1, + TVAR, +}; + +static int +nexttok(const char **pos, struct strbuf *buf) +{ + const char *p; + int r; + + strbuf_reset(buf); + + for (p = *pos; *p;) { + switch (*p) { + case '}': + if (p[1] != '}') + return -EINVAL; + r = strbuf_putc(buf, '}'); + if (r < 0) + return r; + p += 2; + break; + case '{': + if (p[1] == '{') { + r = strbuf_putc(buf, '{'); + if (r < 0) + return r; + p += 2; + continue; + } + *pos = p; + if (buf->len > 0) + return TTEXT; + return TVAR; + case '\\': + switch (*++p) { + case '\\': + r = strbuf_putc(buf, '\\'); + break; + case 'n': + r = strbuf_putc(buf, '\n'); + break; + case 't': + r = strbuf_putc(buf, '\t'); + break; + case '0': + r = strbuf_putc(buf, '\0'); + break; + default: + r = strbuf_putc(buf, '\\'); + if (r < 0) + break; + r = strbuf_putc(buf, *p); + } + if (r < 0) + return r; + p++; + break; + default: + r = strbuf_putc(buf, *p++); + if (r < 0) + return r; + } + } + if (buf->len > 0) { + *pos = p; + return TTEXT; + } + p++; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) +{ + const char *p = *pos; + const char *e; + int r; + bool fill = false; + + spec->conversion = '\0'; + spec->fill = ' '; + spec->align = '>'; + spec->sign = '-'; + spec->width = 0; + spec->precision = 0; + spec->type = '\0'; + + if (*p != '{') + return -EINVAL; + p++; + + e = strpbrk(p, "!:}"); + if (!e) + return -EINVAL; + + strbuf_reset(buf); + r = strbuf_puts(buf, p, e - p); + if (r < 0) + return r; + p = e; + + if (*p == '!') { + spec->conversion = *++p; + p++; + } + + if (*p == ':') { + p++; + if (*p && strchr("<>=", p[1])) { + fill = true; + spec->fill = *p; + spec->align = p[1]; + p += 2; + } else if (strchr("<>=", *p)) { + spec->align = *p; + p += 1; + } + if (strchr("+- ", *p)) { + spec->sign = *p; + p += 1; + } + if ((*p >= '0' && *p <= '9')) { + char *e1; + long v; + if (*p == '0') { + if (!fill) { + spec->fill = '0'; + spec->align = '='; + } + p++; + } + errno = 0; + v = strtoul(p, &e1, 10); + if (errno != 0) + return -errno; + if (v > INT_MAX) + return -ERANGE; + spec->width = v; + p = e1; + } + if (*p == '.') { + char *e1; + long v; + errno = 0; + v = strtoul(p+1, &e1, 10); + if (errno != 0) + return -errno; + if (v > 16) + return -ERANGE; + spec->precision = v; + p = e1; + } + if (*p != '}') + spec->type = *p++; + } + if (*p != '}') + return -EINVAL; + *pos = p+1; + return 0; +} + +struct xbps_fmt * +xbps_fmt_parse(const char *format) +{ + struct strbuf buf = {0}; + const char *pos = format; + struct xbps_fmt *fmt = NULL; + size_t n = 0; + int r = 1; + + for (;;) { + struct xbps_fmt_spec spec; + struct xbps_fmt *tmp; + r = nexttok(&pos, &buf); + if (r < 0) + goto err; + tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); + if (!tmp) + goto err_errno; + fmt = tmp; + switch (r) { + case 0: + fmt[n].common = NULL; + goto out; + case TTEXT: + fmt[n].chunk = calloc(1, sizeof(struct chunk)+buf.len+1); + fmt[n].common->type = TTEXT; + if (!fmt[n].chunk) + goto err_errno; + memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); + break; + case TVAR: + r = parse(&pos, &buf, &spec); + if (r < 0) + goto err; + fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); + if (!fmt[n].var) + goto err_errno; + fmt[n].common->type = TVAR; + fmt[n].var->spec = spec; + memcpy(fmt[n].var->s, buf.mem, buf.len+1); + break; + } + n++; + } +out: + strbuf_release(&buf); + return fmt; +err_errno: + r = -errno; +err: + free(fmt); + strbuf_release(&buf); + errno = -r; + return NULL; +} + +void +xbps_fmt_free(struct xbps_fmt *fmt) +{ + if (!fmt) + return; + for (struct xbps_fmt *f = fmt; f->common; f++) + switch (f->common->type) { + case TTEXT: free(f->chunk); break; + case TVAR: free(f->var); break; + } + free(fmt); +} + +int +xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) +{ + struct strbuf buf = {0}; + const char *pos = format; + int r = 0; + + for (;;) { + struct xbps_fmt_spec spec; + r = nexttok(&pos, &buf); + if (r <= 0) + goto out; + switch (r) { + case TTEXT: + fprintf(fp, "%s", buf.mem); + break; + case TVAR: + r = parse(&pos, &buf, &spec); + if (r < 0) + goto out; + r = cb(fp, &spec, buf.mem, data); + if (r != 0) + goto out; + break; + } + } +out: + strbuf_release(&buf); + return r; +} + +int +xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) +{ + int r; + for (const struct xbps_fmt *f = fmt; f->common; f++) { + switch (f->common->type) { + case TTEXT: + fprintf(fp, "%s", f->chunk->s); + break; + case TVAR: + r = cb(fp, &f->var->spec, f->var->s, data); + if (r != 0) + return r; + break; + } + } + return 0; +} + +struct fmt_dict_cb { + xbps_dictionary_t dict; +}; + +int +xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp) +{ + if (len == 0) + len = strlen(str); + if (spec->align == '>' && spec->width > (unsigned)len) { + for (unsigned i = 0; i < spec->width - len; i++) + fputc(spec->fill, fp); + } + fprintf(fp, "%.*s", (int)len, str); + if (spec->align == '<' && spec->width > (unsigned)len) { + for (unsigned i = 0; i < spec->width - len; i++) + fputc(spec->fill, fp); + } + return 0; +} + +int +xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +{ + char buf[64]; + struct xbps_fmt_spec strspec = *spec; + const char *p = buf; + int len; + int scale; + + if (strspec.align == '=') + strspec.align = '>'; + + switch (spec->type) { + default: /* fallthrough */ + case 'd': + if (spec->sign == '+') + len = snprintf(buf, sizeof(buf), "%+" PRId64, d); + else + len = snprintf(buf, sizeof(buf), "%" PRId64, d); + if (spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { + len--, p++; + strspec.width -= 1; + fputc(buf[0], fp); + } + break; + case 'h': + len = spec->width < sizeof(buf) ? spec->width : sizeof(buf); + scale = humanize_number(buf, len, d, "B", HN_GETSCALE, HN_DECIMAL|HN_IEC_PREFIXES); + if (scale == -1) + return -EINVAL; + if (spec->precision && (unsigned int)scale < 6-spec->precision) + scale = 6-spec->precision; + len = humanize_number(buf, len, d, "B", scale, HN_DECIMAL|HN_IEC_PREFIXES); + if (scale == -1) + return -EINVAL; + break; + case 'o': len = snprintf(buf, sizeof(buf), "%" PRIo64, d); break; + case 'u': len = snprintf(buf, sizeof(buf), "%" PRIu64, d); break; + case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; + case 'X': len = snprintf(buf, sizeof(buf), "%" PRIX64, d); break; + } + return xbps_fmt_string(&strspec, p, len, fp); +} + +static int +fmt_dict_cb(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data) +{ + struct fmt_dict_cb *ctx = data; + xbps_object_t val = xbps_dictionary_get(ctx->dict, var); + + switch (xbps_object_type(val)) { + case XBPS_TYPE_STRING: + return xbps_fmt_string(spec, xbps_string_cstring_nocopy(val), + xbps_string_size(val), fp); + case XBPS_TYPE_NUMBER: + return xbps_fmt_number(spec, xbps_number_integer_value(val), fp); + default: + break; + } + return 0; +} + +int +xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp) +{ + struct fmt_dict_cb ctx = {.dict = dict}; + return xbps_fmt(fmt, &fmt_dict_cb, &ctx, fp); +} + +int +xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp) +{ + struct fmt_dict_cb ctx = {.dict = dict}; + return xbps_fmts(format, &fmt_dict_cb, &ctx, fp); +} From b3ba0f289282289434636594d05063aaba719284 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 20 Feb 2023 23:54:08 +0100 Subject: [PATCH 03/34] bin/xbps-query: add -F/--format flag --- bin/xbps-query/defs.h | 8 +- bin/xbps-query/list.c | 273 ++++++++++++++++++++++-------------------- bin/xbps-query/main.c | 53 ++++++-- 3 files changed, 182 insertions(+), 152 deletions(-) diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 2a1747253..0f4623114 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -52,14 +52,10 @@ int repo_show_pkg_namedesc(struct xbps_handle *, xbps_object_t, void *, int ownedby(struct xbps_handle *, const char *, bool, bool); /* From list.c */ -unsigned int find_longest_pkgver(struct xbps_handle *, xbps_object_t); - -int list_pkgs_in_dict(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); int list_manual_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_hold_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_repolock_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_orphans(struct xbps_handle *); +int list_orphans(struct xbps_handle *, const char *); int list_pkgs_pkgdb(struct xbps_handle *); +int list_pkgdb(struct xbps_handle *, int (*filter)(xbps_object_t), const char *format); int repo_list(struct xbps_handle *); diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index c22ae00de..ff46dda28 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -24,29 +24,59 @@ */ #include -#include + +#include +#include +#include +#include #include +#include #include -#include #include -#include #include "defs.h" +#include "xbps.h" + +struct length_max_cb { + const char *key; + int max; +}; + +static int +length_max_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, + const char *key UNUSED, void *arg, bool *loop_done UNUSED) +{ + struct length_max_cb *ctx = arg; + const char *s = NULL; + size_t len; + + if (!xbps_dictionary_get_cstring_nocopy(obj, ctx->key, &s)) + return -errno; + + len = strlen(s); + if (len > INT_MAX) + return -ERANGE; + if ((int)len > ctx->max) + ctx->max = len; + + return 0; +} struct list_pkgver_cb { - unsigned int pkgver_len; + unsigned int pkgver_align; unsigned int maxcols; - char *linebuf; + char *buf; + struct xbps_fmt *fmt; }; -int -list_pkgs_in_dict(struct xbps_handle *xhp UNUSED, +static int +list_pkgs_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, const char *key UNUSED, void *arg, bool *loop_done UNUSED) { - struct list_pkgver_cb *lpc = arg; + struct list_pkgver_cb *ctx = arg; const char *pkgver = NULL, *short_desc = NULL, *state_str = NULL; unsigned int len; pkg_state_t state; @@ -58,104 +88,107 @@ list_pkgs_in_dict(struct xbps_handle *xhp UNUSED, xbps_pkg_state_dictionary(obj, &state); - if (state == XBPS_PKG_STATE_INSTALLED) - state_str = "ii"; - else if (state == XBPS_PKG_STATE_UNPACKED) - state_str = "uu"; - else if (state == XBPS_PKG_STATE_HALF_REMOVED) - state_str = "hr"; - else - state_str = "??"; - - if (lpc->linebuf == NULL) { - printf("%s %-*s %s\n", - state_str, - lpc->pkgver_len, pkgver, - short_desc); + switch (state) { + case XBPS_PKG_STATE_INSTALLED: state_str = "ii"; break; + case XBPS_PKG_STATE_UNPACKED: state_str = "uu"; break; + case XBPS_PKG_STATE_HALF_REMOVED: state_str = "hr"; break; + case XBPS_PKG_STATE_BROKEN: state_str = "br"; break; + case XBPS_PKG_STATE_NOT_INSTALLED: state_str = "??"; break; + } + + if (!ctx->buf) { + printf("%s %-*s %s\n", state_str, ctx->pkgver_align, pkgver, + short_desc); return 0; } - len = snprintf(lpc->linebuf, lpc->maxcols, "%s %-*s %s", - state_str, - lpc->pkgver_len, pkgver, - short_desc); /* add ellipsis if the line was truncated */ - if (len >= lpc->maxcols && lpc->maxcols > 4) { - for (unsigned int j = 0; j < 3; j++) - lpc->linebuf[lpc->maxcols-j-1] = '.'; - lpc->linebuf[lpc->maxcols] = '\0'; - } - puts(lpc->linebuf); + len = snprintf(ctx->buf, ctx->maxcols, "%s %-*s %s\n", state_str, + ctx->pkgver_align, pkgver, short_desc); + if (len >= ctx->maxcols && ctx->maxcols > sizeof("...")) + memcpy(ctx->buf + ctx->maxcols - sizeof("..."), "...", sizeof("...")); + fputs(ctx->buf, stdout); return 0; } int -list_manual_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) +list_pkgs_pkgdb(struct xbps_handle *xhp) { - const char *pkgver = NULL; - bool automatic = false; + struct length_max_cb longest = {.key = "pkgver"}; + struct list_pkgver_cb lpc = {0}; - xbps_dictionary_get_bool(obj, "automatic-install", &automatic); - if (automatic == false) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); + int r = xbps_pkgdb_foreach_cb_multi(xhp, length_max_cb, &longest); + if (r < 0) + return r; + + lpc.pkgver_align = longest.max; + lpc.maxcols = get_maxcols(); + if (lpc.maxcols > 0) { + lpc.buf = malloc(lpc.maxcols); + if (!lpc.buf) + return -errno; } - return 0; + return xbps_pkgdb_foreach_cb(xhp, list_pkgs_pkgdb_cb, &lpc); } -int -list_hold_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) -{ - const char *pkgver = NULL; +struct list_pkgdb_cb { + struct xbps_fmt *fmt; + int (*filter)(xbps_object_t obj); +}; - if (xbps_dictionary_get(obj, "hold")) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); +static int +list_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, + const char *key UNUSED, void *arg, bool *loop_done UNUSED) +{ + struct list_pkgdb_cb *ctx = arg; + int r; + + if (ctx->filter) { + r = ctx->filter(obj); + if (r < 0) + return r; + if (r == 0) + return 0; } + r = xbps_fmt_dictionary(ctx->fmt, obj, stdout); + if (r < 0) + return r; return 0; } int -list_repolock_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) +list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *format) { - const char *pkgver = NULL; - - if (xbps_dictionary_get(obj, "repolock")) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); + struct list_pkgdb_cb ctx = {.filter = filter}; + int r; + + ctx.fmt = xbps_fmt_parse(format); + if (!ctx.fmt) { + r = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-r)); + return r; } - - return 0; + r = xbps_pkgdb_foreach_cb(xhp, list_pkgdb_cb, &ctx); + xbps_fmt_free(ctx.fmt); + return r; } int -list_orphans(struct xbps_handle *xhp) +list_manual_pkgs(struct xbps_handle *xhp UNUSED, + xbps_object_t obj, + const char *key UNUSED, + void *arg UNUSED, + bool *loop_done UNUSED) { - xbps_array_t orphans; const char *pkgver = NULL; + bool automatic = false; - orphans = xbps_find_pkg_orphans(xhp, NULL); - if (orphans == NULL) - return EINVAL; - - for (unsigned int i = 0; i < xbps_array_count(orphans); i++) { - xbps_dictionary_get_cstring_nocopy(xbps_array_get(orphans, i), - "pkgver", &pkgver); + xbps_dictionary_get_bool(obj, "automatic-install", &automatic); + if (automatic == false) { + xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); puts(pkgver); } @@ -163,22 +196,43 @@ list_orphans(struct xbps_handle *xhp) } int -list_pkgs_pkgdb(struct xbps_handle *xhp) +list_orphans(struct xbps_handle *xhp, const char *format) { - struct list_pkgver_cb lpc; + xbps_array_t orphans; + struct xbps_fmt *fmt; + int r = 0; + + fmt = xbps_fmt_parse(format); + if (!fmt) { + r = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-r)); + return r; + } - lpc.pkgver_len = find_longest_pkgver(xhp, NULL); - lpc.maxcols = get_maxcols(); - lpc.linebuf = NULL; - if (lpc.maxcols > 0) { - lpc.linebuf = malloc(lpc.maxcols); - if (lpc.linebuf == NULL) - exit(1); + orphans = xbps_find_pkg_orphans(xhp, NULL); + if (!orphans) { + r = -errno; + xbps_error_printf("failed to find orphans: %s\n", strerror(-r)); + goto err; } - return xbps_pkgdb_foreach_cb(xhp, list_pkgs_in_dict, &lpc); + for (unsigned int i = 0; i < xbps_array_count(orphans); i++) { + xbps_object_t obj = xbps_array_get(orphans, i); + if (!obj) + return -errno; + r = xbps_fmt_dictionary(fmt, obj, stdout); + if (r < 0) + goto err; + } +err: + xbps_fmt_free(fmt); + return r; } +#ifndef __UNCONST +#define __UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) +#endif + static void repo_list_uri(struct xbps_repo *repo) { @@ -230,50 +284,3 @@ repo_list(struct xbps_handle *xhp) } return 0; } - -struct fflongest { - xbps_dictionary_t d; - unsigned int len; -}; - -static int -_find_longest_pkgver_cb(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg, - bool *loop_done UNUSED) -{ - struct fflongest *ffl = arg; - const char *pkgver = NULL; - unsigned int len; - - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - len = strlen(pkgver); - if (ffl->len == 0 || len > ffl->len) - ffl->len = len; - - return 0; -} - -unsigned int -find_longest_pkgver(struct xbps_handle *xhp, xbps_object_t o) -{ - struct fflongest ffl; - - ffl.d = o; - ffl.len = 0; - - if (xbps_object_type(o) == XBPS_TYPE_DICTIONARY) { - xbps_array_t array; - - array = xbps_dictionary_all_keys(o); - (void)xbps_array_foreach_cb_multi(xhp, array, o, - _find_longest_pkgver_cb, &ffl); - xbps_object_release(array); - } else { - (void)xbps_pkgdb_foreach_cb_multi(xhp, - _find_longest_pkgver_cb, &ffl); - } - - return ffl.len; -} diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 44316c1ad..42afddb99 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -23,13 +23,15 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include #include #include #include -#include -#include #include + #include "defs.h" static void __attribute__((noreturn)) @@ -41,6 +43,7 @@ usage(bool fail) " -C, --config Path to confdir (xbps.d)\n" " -c, --cachedir Path to cachedir\n" " -d, --debug Debug mode shown to stderr\n" + " -F, --format Format for list output\n" " -h, --help Show usage\n" " -i, --ignore-conf-repos Ignore repositories defined in xbps.d\n" " -M, --memory-sync Remote repository data is fetched and stored\n" @@ -74,10 +77,30 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } +static int +filter_hold(xbps_object_t obj) +{ + return xbps_dictionary_get(obj, "hold") != NULL; +} + +static int +filter_manual(xbps_object_t obj) +{ + bool automatic = false; + xbps_dictionary_get_bool(obj, "automatic-install", &automatic); + return !automatic; +} + +static int +filter_repolock(xbps_object_t obj) +{ + return xbps_dictionary_get(obj, "repolock") != NULL; +} + int main(int argc, char **argv) { - const char *shortopts = "C:c:df:hHiLlMmOo:p:Rr:s:S:VvX:x:"; + const char *shortopts = "C:c:dF:f:hHiLlMmOo:p:Rr:s:S:VvX:x:"; const struct option longopts[] = { { "config", required_argument, NULL, 'C' }, { "cachedir", required_argument, NULL, 'c' }, @@ -100,6 +123,7 @@ main(int argc, char **argv) { "version", no_argument, NULL, 'V' }, { "verbose", no_argument, NULL, 'v' }, { "files", required_argument, NULL, 'f' }, + { "format", required_argument, NULL, 'F' }, { "deps", required_argument, NULL, 'x' }, { "revdeps", required_argument, NULL, 'X' }, { "regex", no_argument, NULL, 0 }, @@ -108,13 +132,13 @@ main(int argc, char **argv) { NULL, 0, NULL, 0 }, }; struct xbps_handle xh; - const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile; + const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile, *format; int c, flags, rv; bool list_pkgs, list_repos, orphans, own, list_repolock; bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; - rootdir = cachedir = confdir = props = pkg = catfile = NULL; + rootdir = cachedir = confdir = props = pkg = catfile = format = NULL; flags = rv = c = 0; list_pkgs = list_repos = list_hold = orphans = pkg_search = own = false; list_manual = list_repolock = show_prop = show_files = false; @@ -138,6 +162,9 @@ main(int argc, char **argv) pkg = optarg; show_files = opmode = true; break; + case 'F': + format = optarg; + break; case 'H': list_hold = opmode = true; break; @@ -259,24 +286,24 @@ main(int argc, char **argv) rv = repo_list(&xh); } else if (list_hold) { - /* list on hold pkgs */ - rv = xbps_pkgdb_foreach_cb(&xh, list_hold_pkgs, NULL); + rv = list_pkgdb(&xh, filter_hold, format ? format : "{pkgver}\n") < 0; } else if (list_repolock) { - /* list repolocked packages */ - rv = xbps_pkgdb_foreach_cb(&xh, list_repolock_pkgs, NULL); + rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n") < 0; } else if (list_manual) { - /* list manual pkgs */ - rv = xbps_pkgdb_foreach_cb(&xh, list_manual_pkgs, NULL); + rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n") < 0; } else if (list_pkgs) { /* list available pkgs */ - rv = list_pkgs_pkgdb(&xh); + if (format) + rv = list_pkgdb(&xh, NULL, format); + else + rv = list_pkgs_pkgdb(&xh); } else if (orphans) { /* list pkg orphans */ - rv = list_orphans(&xh); + rv = list_orphans(&xh, format ? format : "{pkgver}\n") < 0; } else if (own) { /* ownedby mode */ From 0813ea706e575bd64eced9d1c872dd7d5113a475 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 15:56:27 +0100 Subject: [PATCH 04/34] tests: add tests for xbps_fmt* functions --- tests/xbps/libxbps/Kyuafile | 1 + tests/xbps/libxbps/Makefile | 1 + tests/xbps/libxbps/fmt/Kyuafile | 5 + tests/xbps/libxbps/fmt/Makefile | 8 ++ tests/xbps/libxbps/fmt/main.c | 180 ++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 tests/xbps/libxbps/fmt/Kyuafile create mode 100644 tests/xbps/libxbps/fmt/Makefile create mode 100644 tests/xbps/libxbps/fmt/main.c diff --git a/tests/xbps/libxbps/Kyuafile b/tests/xbps/libxbps/Kyuafile index 86a009d9e..050e80733 100644 --- a/tests/xbps/libxbps/Kyuafile +++ b/tests/xbps/libxbps/Kyuafile @@ -12,3 +12,4 @@ include('config/Kyuafile') include('find_pkg_orphans/Kyuafile') include('pkgdb/Kyuafile') include('shell/Kyuafile') +include('fmt/Kyuafile') diff --git a/tests/xbps/libxbps/Makefile b/tests/xbps/libxbps/Makefile index ca361bc65..17bcd132e 100644 --- a/tests/xbps/libxbps/Makefile +++ b/tests/xbps/libxbps/Makefile @@ -12,5 +12,6 @@ SUBDIRS += find_pkg_orphans SUBDIRS += pkgdb SUBDIRS += config SUBDIRS += shell +SUBDIRS += fmt include ../../../mk/subdir.mk diff --git a/tests/xbps/libxbps/fmt/Kyuafile b/tests/xbps/libxbps/fmt/Kyuafile new file mode 100644 index 000000000..1a426bbdb --- /dev/null +++ b/tests/xbps/libxbps/fmt/Kyuafile @@ -0,0 +1,5 @@ +syntax("kyuafile", 1) + +test_suite("libxbps") + +atf_test_program{name="fmt_test"} diff --git a/tests/xbps/libxbps/fmt/Makefile b/tests/xbps/libxbps/fmt/Makefile new file mode 100644 index 000000000..c7749f64b --- /dev/null +++ b/tests/xbps/libxbps/fmt/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../.. +-include $(TOPDIR)/config.mk + +TESTSSUBDIR = xbps/libxbps/fmt +TEST = fmt_test +EXTRA_FILES = Kyuafile + +include $(TOPDIR)/mk/test.mk diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c new file mode 100644 index 000000000..38b98c723 --- /dev/null +++ b/tests/xbps/libxbps/fmt/main.c @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 2023 Duncan Overbruck . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *- + */ +#include "xbps.h" +#include "xbps/xbps_dictionary.h" +#include "xbps/xbps_object.h" +#include +#include +#include +#include +#include + +#include +#include + +ATF_TC(xbps_fmt_number); + +ATF_TC_HEAD(xbps_fmt_number, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_number"); +} + +ATF_TC_BODY(xbps_fmt_number, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct test { + const char *expect; + int64_t d; + struct xbps_fmt_spec spec; + } tests[] = { + { "1", 1, {0}}, + {"-1", -1, {0}}, + {"-1", -1, {.sign = '+'}}, + {"+1", 1, {.sign = '+'}}, + + {"a", 0xA, {.type = 'x'}}, + {"A", 0xA, {.type = 'X'}}, + + {"644", 0644, {.type = 'o'}}, + + {"0010", 10, {.fill = '0', .align = '>', .width = 4}}, + {"1000", 10, {.fill = '0', .align = '<', .width = 4}}, + {"0010", 10, {.fill = '0', .align = '=', .width = 4}}, + {"-010", -10, {.fill = '0', .align = '=', .width = 4}}, + {"+010", 10, {.fill = '0', .align = '=', .sign = '+', .width = 4}}, + }; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + + for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + memset(buf, '\0', bufsz); + rewind(fp); + xbps_fmt_number(&tests[i].spec, tests[i].d, fp); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, tests[i].expect); + } + ATF_REQUIRE(fclose(fp) == 0); + free(buf); +} + +ATF_TC(xbps_fmt_string); + +ATF_TC_HEAD(xbps_fmt_string, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_string"); +} + +ATF_TC_BODY(xbps_fmt_string, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct test { + const char *expect; + const char *input; + size_t len; + struct xbps_fmt_spec spec; + } tests[] = { + { "1", "1", 0, {0}}, + { "2 ", "2", 0, {.fill = ' ', .align = '<', .width = 2}}, + { " 3", "3", 0, {.fill = ' ', .align = '>', .width = 2}}, + { "444", "444", 0, {.fill = ' ', .align = '>', .width = 2}}, + { "44", "444", 2, {.fill = ' ', .align = '>', .width = 2}}, + }; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + + for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + memset(buf, '\0', bufsz); + rewind(fp); + xbps_fmt_string(&tests[i].spec, tests[i].input, tests[i].len, fp); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, tests[i].expect); + } + ATF_REQUIRE(fclose(fp) == 0); + free(buf); +} + +ATF_TC(xbps_fmt_dictionary); + +ATF_TC_HEAD(xbps_fmt_dictionary, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_dictionary"); +} + +ATF_TC_BODY(xbps_fmt_dictionary, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct xbps_fmt *fmt; + xbps_dictionary_t dict; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + ATF_REQUIRE(dict = xbps_dictionary_create()); + ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); + ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number}<")); + ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_REQUIRE(fclose(fp) == 0); + free(buf); + xbps_object_release(dict); +} + +ATF_TC(xbps_fmts_dictionary); + +ATF_TC_HEAD(xbps_fmts_dictionary, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_dictionary"); +} + +ATF_TC_BODY(xbps_fmts_dictionary, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + xbps_dictionary_t dict; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + ATF_REQUIRE(dict = xbps_dictionary_create()); + ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); + ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); + ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number}<", dict, fp) == 0); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_REQUIRE(fclose(fp) == 0); + free(buf); + xbps_object_release(dict); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, xbps_fmt_number); + ATF_TP_ADD_TC(tp, xbps_fmt_string); + ATF_TP_ADD_TC(tp, xbps_fmt_dictionary); + ATF_TP_ADD_TC(tp, xbps_fmts_dictionary); + return atf_no_error(); +} From 5972c114b496548c35272af62847b90fdc6d6059 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 17:42:36 +0100 Subject: [PATCH 05/34] lib/format.c: new humanize format !humanize[ ][.][width][minscale[maxscale]][i] --- include/xbps.h.in | 3 +- lib/format.c | 181 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 142 insertions(+), 42 deletions(-) diff --git a/include/xbps.h.in b/include/xbps.h.in index 134da0e41..cf05b77bc 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2385,10 +2385,11 @@ struct xbps_fmt; */ struct xbps_fmt_spec { /** + * @private * @var conversion * @brief Output conversion. */ - char conversion; + struct conversion *conversion; /** * @var fill * @brief Padding character. diff --git a/lib/format.c b/lib/format.c index 80f8902cb..c17cf28f2 100644 --- a/lib/format.c +++ b/lib/format.c @@ -197,15 +197,44 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } +struct conversion { + enum { HUMANIZE = 1, STRMODE } type; + union { + struct humanize { + unsigned width : 8; + unsigned minscale : 8; + unsigned maxscale : 8; + bool decimal : 1; + int flags; + } humanize; + }; +}; + static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) +parse_u(const char **pos, unsigned int *u) +{ + char *e = NULL; + long v; + errno = 0; + v = strtoul(*pos, &e, 10); + if (errno != 0) + return -errno; + if (v > UINT_MAX) + return -ERANGE; + *u = v; + *pos = e; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) { const char *p = *pos; const char *e; int r; bool fill = false; - spec->conversion = '\0'; + spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; spec->sign = '-'; @@ -228,8 +257,53 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) p = e; if (*p == '!') { - spec->conversion = *++p; - p++; + if (strncmp(p+1, "humanize", sizeof("humanize") - 1) == 0) { + /* humanize[ ][.][i][width][minscale[maxscale]] */ + const char *scale = "BKMGTPE"; + const char *p1; + p += sizeof("humanize"); + conversion->type = HUMANIZE; + if (*p != ':' && *p != '}') { + conversion->humanize.flags = HN_NOSPACE; + if (*p == ' ') { + conversion->humanize.flags &= ~HN_NOSPACE; + p++; + } + if (*p == '.') { + conversion->humanize.flags |= HN_DECIMAL; + p++; + } + if ((*p >= '0' && *p <= '9')) { + unsigned width = 0; + r = parse_u(&p, &width); + if (r < 0) + return r; + conversion->humanize.width = width <= 12 ? width : 12; + } + if ((p1 = strchr(scale, *p))) { + conversion->humanize.minscale = p1-scale+1; + p++; + if ((p1 = strchr(scale, *p))) { + conversion->humanize.maxscale = p1-scale+1; + p++; + } + } + if (*p == 'i') { + conversion->humanize.flags |= HN_IEC_PREFIXES; + p++; + } + } else { + /* default: !humanize .8Ki:8 */ + conversion->humanize.width = 8; + conversion->humanize.minscale = 2; + conversion->humanize.flags = HN_DECIMAL|HN_IEC_PREFIXES; + } + } else if (strncmp(p+1, "strmode", sizeof("strmode") - 1) == 0) { + p += sizeof("strmode"); + conversion->type = STRMODE; + } else { + return -EINVAL; + } } if (*p == ':') { @@ -248,8 +322,6 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) p += 1; } if ((*p >= '0' && *p <= '9')) { - char *e1; - long v; if (*p == '0') { if (!fill) { spec->fill = '0'; @@ -257,26 +329,15 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) } p++; } - errno = 0; - v = strtoul(p, &e1, 10); - if (errno != 0) - return -errno; - if (v > INT_MAX) - return -ERANGE; - spec->width = v; - p = e1; + r = parse_u(&p, &spec->width); + if (r < 0) + return r; } if (*p == '.') { - char *e1; - long v; - errno = 0; - v = strtoul(p+1, &e1, 10); - if (errno != 0) - return -errno; - if (v > 16) - return -ERANGE; - spec->precision = v; - p = e1; + p++; + r = parse_u(&p, &spec->precision); + if (r < 0) + return r; } if (*p != '}') spec->type = *p++; @@ -297,7 +358,8 @@ xbps_fmt_parse(const char *format) int r = 1; for (;;) { - struct xbps_fmt_spec spec; + struct xbps_fmt_spec spec = {0}; + struct conversion conversion = {0}; struct xbps_fmt *tmp; r = nexttok(&pos, &buf); if (r < 0) @@ -318,7 +380,7 @@ xbps_fmt_parse(const char *format) memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); break; case TVAR: - r = parse(&pos, &buf, &spec); + r = parse(&pos, &buf, &spec, &conversion); if (r < 0) goto err; fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); @@ -327,6 +389,12 @@ xbps_fmt_parse(const char *format) fmt[n].common->type = TVAR; fmt[n].var->spec = spec; memcpy(fmt[n].var->s, buf.mem, buf.len+1); + if (conversion.type) { + fmt[n].var->spec.conversion = calloc(1, sizeof(struct conversion)); + if (!fmt[n].var->spec.conversion) + goto err_errno; + *fmt[n].var->spec.conversion = conversion; + } break; } n++; @@ -351,7 +419,10 @@ xbps_fmt_free(struct xbps_fmt *fmt) for (struct xbps_fmt *f = fmt; f->common; f++) switch (f->common->type) { case TTEXT: free(f->chunk); break; - case TVAR: free(f->var); break; + case TVAR: + free(f->var->spec.conversion); + free(f->var); + break; } free(fmt); } @@ -364,7 +435,8 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) int r = 0; for (;;) { - struct xbps_fmt_spec spec; + struct xbps_fmt_spec spec = {0}; + struct conversion conversion = {0}; r = nexttok(&pos, &buf); if (r <= 0) goto out; @@ -373,7 +445,7 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) fprintf(fp, "%s", buf.mem); break; case TVAR: - r = parse(&pos, &buf, &spec); + r = parse(&pos, &buf, &spec, &conversion); if (r < 0) goto out; r = cb(fp, &spec, buf.mem, data); @@ -427,6 +499,38 @@ xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, F return 0; } +static int +humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +{ + char buf[64]; + const struct humanize *h = &spec->conversion->humanize; + int scale = 0; + int width = h->width ? h->width : 8; + int len; + + if (h->minscale) { + scale = humanize_number(buf, width, d, "B", HN_GETSCALE, h->flags); + if (scale == -1) + return -EINVAL; + if (scale < h->minscale - 1) + scale = h->minscale - 1; + if (h->maxscale && scale > h->maxscale - 1) + scale = h->maxscale - 1; + } else if (scale == 0) { + scale = HN_AUTOSCALE; + } + len = humanize_number(buf, width, d, "B", scale, h->flags); + if (len == -1) + return -EINVAL; + return xbps_fmt_string(spec, buf, len, fp); +} + +static int +tostrmode(const struct xbps_fmt_spec *spec UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +{ + return -ENOTSUP; +} + int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) { @@ -434,7 +538,13 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) struct xbps_fmt_spec strspec = *spec; const char *p = buf; int len; - int scale; + + if (spec->conversion) { + switch (spec->conversion->type) { + case HUMANIZE: return humanize(spec, d, fp); + case STRMODE: return tostrmode(spec, d, fp); + } + } if (strspec.align == '=') strspec.align = '>'; @@ -452,17 +562,6 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) fputc(buf[0], fp); } break; - case 'h': - len = spec->width < sizeof(buf) ? spec->width : sizeof(buf); - scale = humanize_number(buf, len, d, "B", HN_GETSCALE, HN_DECIMAL|HN_IEC_PREFIXES); - if (scale == -1) - return -EINVAL; - if (spec->precision && (unsigned int)scale < 6-spec->precision) - scale = 6-spec->precision; - len = humanize_number(buf, len, d, "B", scale, HN_DECIMAL|HN_IEC_PREFIXES); - if (scale == -1) - return -EINVAL; - break; case 'o': len = snprintf(buf, sizeof(buf), "%" PRIo64, d); break; case 'u': len = snprintf(buf, sizeof(buf), "%" PRIu64, d); break; case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; From 03b4ab1e22eeeee103231925b7a4ad0d429b5859 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 18:46:11 +0100 Subject: [PATCH 06/34] bin/xbps-query: document format flag --- bin/xbps-query/xbps-query.1 | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index b4c364180..ed475db5e 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -88,6 +88,49 @@ If the first character is not '/' then it's a relative path of .Ar rootdir . .It Fl d, Fl -debug Enables extra debugging shown to stderr. +.It Fl F, Fl -format Ar format +Format for list output. +.Bd -literal + ::= "{" variable ["!" conversion] [":" format] "}" + ::= [a-zA-Z90-9_-] + + ::= humanize | strmode + +-- Convert inode status information into a symbolic string + ::= "strmode" + +-- Format a number into a human readable form, the default is:`humanize .8Ki`: + ::= "humanize" [space] [decimal] [width] [scale] [i] + ::= " " -- Put a space between number and the suffix. + ::= "." -- If the final result is less than 10, display + it using one digit. + ::= [0-9]+ -- Width of the output. + ::= multiplier -- Minimum scale multiplier and optionally + [multiplier] -- Maxium scale multiplier. + ::= "B" + | "K" -- kilo + | "M" -- mega + | "G" -- giga + | "T" -- tera + | "P" -- peta + | "E" -- exa + ::= "i" -- Divide number with 1000 instead of 1024. + + ::= [[fill] align] [sign] [width] ["." precision] [type] + ::= -- The character to use when aligning the output. + ::= "<" -- Left align. + | ">" -- Right align. + | "=" -- Left align with zero paddings after the sign. + ::= "+" -- Add sign to positive and negative numbers. + | "-" -- Add sign to negative numbers. + ::= [0-9]+ -- The alignment width. + ::= [0-9]+ -- Percision for numbers. + ::= "d" -- Decimal number. + | "o" -- Octal number. + | "u" -- Unsigned number. + | "x" -- Hexadecimal with lowercase letters. + | "X" -- Hexadecimal with uppercase letters. +.Ed .It Fl h, Fl -help Show the help message. .It Fl i, Fl -ignore-conf-repos From 870b70c3052f78d54934cd70a3e42a2258189c30 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 15:58:55 +0100 Subject: [PATCH 07/34] lib/format.c: split/cleanup code --- lib/format.c | 240 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 90 deletions(-) diff --git a/lib/format.c b/lib/format.c index c17cf28f2..00a213b37 100644 --- a/lib/format.c +++ b/lib/format.c @@ -227,13 +227,76 @@ parse_u(const char **pos, unsigned int *u) } static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +parse_humanize(const char **pos, struct humanize *humanize) { + const char *scale = "BKMGTPE"; const char *p = *pos; - const char *e; - int r; + const char *p1; + + /* default: !humanize .8Ki:8 */ + humanize->width = 8; + humanize->minscale = 2; + humanize->flags = HN_DECIMAL|HN_IEC_PREFIXES; + humanize->flags = HN_NOSPACE; + + /* humanize[ ][.][i][width][minscale[maxscale]] */ + + if (*p == ' ') { + humanize->flags &= ~HN_NOSPACE; + p++; + } + if (*p == '.') { + humanize->flags |= HN_DECIMAL; + p++; + } + if ((*p >= '0' && *p <= '9')) { + unsigned width = 0; + int r = parse_u(&p, &width); + if (r < 0) + return r; + humanize->width = width <= 12 ? width : 12; + } + if ((p1 = strchr(scale, *p))) { + humanize->minscale = p1-scale+1; + p++; + if ((p1 = strchr(scale, *p))) { + humanize->maxscale = p1-scale+1; + p++; + } + } + if (*p == 'i') { + humanize->flags |= HN_IEC_PREFIXES; + p++; + } + *pos = p; + return 0; +} + +static int +parse_conversion(const char **pos, struct conversion *conversion) +{ + if (**pos != '!') + return 0; + if (strncmp(*pos + 1, "strmode", sizeof("strmode") - 1) == 0) { + *pos += sizeof("strmode"); + conversion->type = STRMODE; + return 0; + } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { + conversion->type = HUMANIZE; + *pos += sizeof("humanize"); + return parse_humanize(pos, &conversion->humanize); + } + return -EINVAL; +} + +static int +parse_spec(const char **pos, struct xbps_fmt_spec *spec) +{ bool fill = false; + const char *p = *pos; + int r; + /* defaults */ spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; @@ -242,12 +305,85 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c spec->precision = 0; spec->type = '\0'; + /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ + + if (*p != ':') + return 0; + p++; + + /* fill ::= . */ + if (*p && strchr("<>=", p[1])) { + fill = true; + spec->fill = *p; + spec->align = p[1]; + p += 2; + } + + /* align ::= [<>=] */ + if (strchr("<>=", *p)) { + spec->align = *p; + p += 1; + } + + /* sign ::= [+-] */ + if (strchr("+- ", *p)) { + spec->sign = *p; + p += 1; + } + + /* zero ::= [0] */ + if (*p == '0') { + if (!fill) { + spec->fill = '0'; + spec->align = '='; + } + p++; + } + + /* width ::= [[0-9]+] */ + if ((*p >= '0' && *p <= '9')) { + r = parse_u(&p, &spec->width); + if (r < 0) + return r; + } + + /* precision ::= ['.' [0-9]+] */ + if (*p == '.') { + p++; + r = parse_u(&p, &spec->precision); + if (r < 0) + return r; + } + + /* type ::= [[a-zA-Z]] */ + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) + spec->type = *p++; + + *pos = p; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +{ + const char *p = *pos; + const char *e; + int r; + if (*p != '{') return -EINVAL; p++; - e = strpbrk(p, "!:}"); - if (!e) + /* var ::= '{' name [conversion][format_spec] '}' */ + + /* name ::= [a-zA-Z0-9_-]+ */ + for (e = p; (*e >= 'a' && *e <= 'z') || + (*e >= 'A' && *e <= 'Z') || + (*e >= '0' && *e <= '0') || + (*e == '_' || *e == '-'); e++) + ; + if (e == p) return -EINVAL; strbuf_reset(buf); @@ -256,92 +392,16 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c return r; p = e; - if (*p == '!') { - if (strncmp(p+1, "humanize", sizeof("humanize") - 1) == 0) { - /* humanize[ ][.][i][width][minscale[maxscale]] */ - const char *scale = "BKMGTPE"; - const char *p1; - p += sizeof("humanize"); - conversion->type = HUMANIZE; - if (*p != ':' && *p != '}') { - conversion->humanize.flags = HN_NOSPACE; - if (*p == ' ') { - conversion->humanize.flags &= ~HN_NOSPACE; - p++; - } - if (*p == '.') { - conversion->humanize.flags |= HN_DECIMAL; - p++; - } - if ((*p >= '0' && *p <= '9')) { - unsigned width = 0; - r = parse_u(&p, &width); - if (r < 0) - return r; - conversion->humanize.width = width <= 12 ? width : 12; - } - if ((p1 = strchr(scale, *p))) { - conversion->humanize.minscale = p1-scale+1; - p++; - if ((p1 = strchr(scale, *p))) { - conversion->humanize.maxscale = p1-scale+1; - p++; - } - } - if (*p == 'i') { - conversion->humanize.flags |= HN_IEC_PREFIXES; - p++; - } - } else { - /* default: !humanize .8Ki:8 */ - conversion->humanize.width = 8; - conversion->humanize.minscale = 2; - conversion->humanize.flags = HN_DECIMAL|HN_IEC_PREFIXES; - } - } else if (strncmp(p+1, "strmode", sizeof("strmode") - 1) == 0) { - p += sizeof("strmode"); - conversion->type = STRMODE; - } else { - return -EINVAL; - } - } + /* conversion ::= ['!' ...] */ + r = parse_conversion(&p, conversion); + if (r < 0) + return r; + + /* format_spec ::= [':' ...] */ + r = parse_spec(&p, spec); + if (r < 0) + return r; - if (*p == ':') { - p++; - if (*p && strchr("<>=", p[1])) { - fill = true; - spec->fill = *p; - spec->align = p[1]; - p += 2; - } else if (strchr("<>=", *p)) { - spec->align = *p; - p += 1; - } - if (strchr("+- ", *p)) { - spec->sign = *p; - p += 1; - } - if ((*p >= '0' && *p <= '9')) { - if (*p == '0') { - if (!fill) { - spec->fill = '0'; - spec->align = '='; - } - p++; - } - r = parse_u(&p, &spec->width); - if (r < 0) - return r; - } - if (*p == '.') { - p++; - r = parse_u(&p, &spec->precision); - if (r < 0) - return r; - } - if (*p != '}') - spec->type = *p++; - } if (*p != '}') return -EINVAL; *pos = p+1; From 0982b22b3506deb577f3d1fe3a45bd8f96660b29 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:39:25 +0100 Subject: [PATCH 08/34] lib/format.c: change escaped [{}] to \\[{}] --- lib/format.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/format.c b/lib/format.c index 00a213b37..bae80a42c 100644 --- a/lib/format.c +++ b/lib/format.c @@ -140,21 +140,8 @@ nexttok(const char **pos, struct strbuf *buf) for (p = *pos; *p;) { switch (*p) { case '}': - if (p[1] != '}') - return -EINVAL; - r = strbuf_putc(buf, '}'); - if (r < 0) - return r; - p += 2; - break; + return -EINVAL; case '{': - if (p[1] == '{') { - r = strbuf_putc(buf, '{'); - if (r < 0) - return r; - p += 2; - continue; - } *pos = p; if (buf->len > 0) return TTEXT; @@ -173,6 +160,12 @@ nexttok(const char **pos, struct strbuf *buf) case '0': r = strbuf_putc(buf, '\0'); break; + case '{': + r = strbuf_putc(buf, '{'); + break; + case '}': + r = strbuf_putc(buf, '}'); + break; default: r = strbuf_putc(buf, '\\'); if (r < 0) From c5388cda2e38c7cb27acc6da08bf90d1cdd669c1 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:46:34 +0100 Subject: [PATCH 09/34] lib/format.c: add some more escape sequences --- lib/format.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/format.c b/lib/format.c index bae80a42c..0db817ae5 100644 --- a/lib/format.c +++ b/lib/format.c @@ -148,24 +148,16 @@ nexttok(const char **pos, struct strbuf *buf) return TVAR; case '\\': switch (*++p) { - case '\\': - r = strbuf_putc(buf, '\\'); - break; - case 'n': - r = strbuf_putc(buf, '\n'); - break; - case 't': - r = strbuf_putc(buf, '\t'); - break; - case '0': - r = strbuf_putc(buf, '\0'); - break; - case '{': - r = strbuf_putc(buf, '{'); - break; - case '}': - r = strbuf_putc(buf, '}'); - break; + case '\\': r = strbuf_putc(buf, '\\'); break; + case 'a': r = strbuf_putc(buf, '\a'); break; + case 'b': r = strbuf_putc(buf, '\b'); break; + case 'f': r = strbuf_putc(buf, '\f'); break; + case 'n': r = strbuf_putc(buf, '\n'); break; + case 'r': r = strbuf_putc(buf, '\r'); break; + case 't': r = strbuf_putc(buf, '\t'); break; + case '0': r = strbuf_putc(buf, '\0'); break; + case '{': r = strbuf_putc(buf, '{'); break; + case '}': r = strbuf_putc(buf, '}'); break; default: r = strbuf_putc(buf, '\\'); if (r < 0) From 574938ddca1e3186bb5f168aaea90aaba2d9a357 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:53:33 +0100 Subject: [PATCH 10/34] bin/xbps-query: update/clean format documentation --- bin/xbps-query/xbps-query.1 | 110 ++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index ed475db5e..cfc196e2b 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -89,48 +89,10 @@ If the first character is not '/' then it's a relative path of .It Fl d, Fl -debug Enables extra debugging shown to stderr. .It Fl F, Fl -format Ar format -Format for list output. -.Bd -literal - ::= "{" variable ["!" conversion] [":" format] "}" - ::= [a-zA-Z90-9_-] - - ::= humanize | strmode - --- Convert inode status information into a symbolic string - ::= "strmode" - --- Format a number into a human readable form, the default is:`humanize .8Ki`: - ::= "humanize" [space] [decimal] [width] [scale] [i] - ::= " " -- Put a space between number and the suffix. - ::= "." -- If the final result is less than 10, display - it using one digit. - ::= [0-9]+ -- Width of the output. - ::= multiplier -- Minimum scale multiplier and optionally - [multiplier] -- Maxium scale multiplier. - ::= "B" - | "K" -- kilo - | "M" -- mega - | "G" -- giga - | "T" -- tera - | "P" -- peta - | "E" -- exa - ::= "i" -- Divide number with 1000 instead of 1024. - - ::= [[fill] align] [sign] [width] ["." precision] [type] - ::= -- The character to use when aligning the output. - ::= "<" -- Left align. - | ">" -- Right align. - | "=" -- Left align with zero paddings after the sign. - ::= "+" -- Add sign to positive and negative numbers. - | "-" -- Add sign to negative numbers. - ::= [0-9]+ -- The alignment width. - ::= [0-9]+ -- Percision for numbers. - ::= "d" -- Decimal number. - | "o" -- Octal number. - | "u" -- Unsigned number. - | "x" -- Hexadecimal with lowercase letters. - | "X" -- Hexadecimal with uppercase letters. -.Ed +Format string for output formatting. +See +.Sx FORMAT STRINGS +for syntax. .It Fl h, Fl -help Show the help message. .It Fl i, Fl -ignore-conf-repos @@ -320,6 +282,70 @@ expression wins. This expects an absolute path. This mode only works with repositories. .El +.Sh FORMAT STRINGS +Variables are package properties if not otherwise documented. +See +.Sx PROPERTIES +section for a list of available properties. +.Pp +As example a format string like: +.Bd -offset indent -literal +{pkgname:<30} {installed_size!humanize :>10}\\n +.Ed +.Pp +Would produce a list formatted like: +.Bd -offset indent -literal +libxbps 304 KB +xbps 484 KB +.Ed +.Pp +Format strings are parsed by the following EBNF: +.Bd -literal + ::= (text | escape | substitution)* + ::= [^\\{}]+ -- literal text chunk + ::= "\\" [abfnrtv0] -- POSIX-like espace sequence + | "\\{" | "\\}" -- escaped "{" and "}" + + ::= "{" variable ["!" conversion] [":" format] "}" + ::= [a-zA-Z0-9_-] + + ::= humanize | strmode + +-- Convert inode status information into a symbolic string + ::= "strmode" + +-- Format a number into a human readable form, the default is:`humanize .8Ki`: + ::= "humanize" [space] [decimal] [width] [scale] [i] + ::= " " -- Put a space between number and the suffix. + ::= "." -- If the final result is less than 10, display + it using one digit. + ::= [0-9]+ -- Width of the output. + ::= multiplier -- Minimum scale multiplier and optionally + [multiplier] -- Maxium scale multiplier. + ::= "B" + | "K" -- kilo + | "M" -- mega + | "G" -- giga + | "T" -- tera + | "P" -- peta + | "E" -- exa + ::= "i" -- Divide number with 1000 instead of 1024. + + ::= [[fill] align] [sign] [width] ["." precision] [type] + ::= -- The character to use when aligning the output. + ::= "<" -- Left align. + | ">" -- Right align. + | "=" -- Left align with zero paddings after the sign. + ::= "+" -- Add sign to positive and negative numbers. + | "-" -- Add sign to negative numbers. + ::= [0-9]+ -- The alignment width. + ::= [0-9]+ -- Percision for numbers. + ::= "d" -- Decimal number. + | "o" -- Octal number. + | "u" -- Unsigned number. + | "x" -- Hexadecimal with lowercase letters. + | "X" -- Hexadecimal with uppercase letters. +.Ed .Sh PROPERTIES This is the list of a packages properties. Note that not all properties are available for all packages. From c95656e7855b395461cf44c893b6c1e191f03e69 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 17:18:16 +0100 Subject: [PATCH 11/34] bin/xbps-query: fix humanize "i" SI prefixs documentation --- bin/xbps-query/xbps-query.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index cfc196e2b..9aa6a8cc1 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -322,14 +322,14 @@ Format strings are parsed by the following EBNF: ::= [0-9]+ -- Width of the output. ::= multiplier -- Minimum scale multiplier and optionally [multiplier] -- Maxium scale multiplier. - ::= "B" + ::= "B" -- byte | "K" -- kilo | "M" -- mega | "G" -- giga | "T" -- tera | "P" -- peta | "E" -- exa - ::= "i" -- Divide number with 1000 instead of 1024. + ::= "i" -- Use IEE/IEC (and now also SI) power of two prefixes. ::= [[fill] align] [sign] [width] ["." precision] [type] ::= -- The character to use when aligning the output. From 16afd800a2a40f8b78dfe8d431fb4bf83bea3555 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 17:02:30 +0100 Subject: [PATCH 12/34] lib/format.c: refactor a bit --- include/xbps.h.in | 51 +++++-- lib/format.c | 263 +++++++++++++++++----------------- tests/xbps/libxbps/fmt/main.c | 34 ++--- 3 files changed, 189 insertions(+), 159 deletions(-) diff --git a/include/xbps.h.in b/include/xbps.h.in index cf05b77bc..a89e3d415 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2377,19 +2377,35 @@ xbps_plist_dictionary_from_file(const char *path); * @struct xbps_fmt xbps.h "xbps.h" * @brief Structure of parsed format string. */ -struct xbps_fmt; +struct xbps_fmt { + /** + * @private + * @var prefix + * @brief Prefix of the format chunk. + */ + char *prefix; + /** + * @var var + * @brief Variable name. + */ + char *var; + /** + * @var conv + * @brief Format conversion. + */ + struct xbps_fmt_conv *conv; + /** + * @var spec + * @brief Format specification. + */ + struct xbps_fmt_spec *spec; +}; /** * @struct xbps_fmt xbps.h "xbps.h" * @brief Structure of parsed format specifier. */ struct xbps_fmt_spec { - /** - * @private - * @var conversion - * @brief Output conversion. - */ - struct conversion *conversion; /** * @var fill * @brief Padding character. @@ -2450,7 +2466,7 @@ struct xbps_fmt_spec { * @param[in] var The format string variable name. * @param[in] data Userdata passed to the xbps_fmt() function. */ -typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data); +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt *fmt, void *data); /** * @brief Parses the format string \a format. @@ -2537,7 +2553,7 @@ int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); /** * @brief Print formatted number to \a fp. * - * Prints the number \d to \a fp according to the specification \a spec. + * Prints the number \a num to \a fp according to the specification \a spec. * * @param[in] spec Format specification. * @param[in] num Number to print. @@ -2545,7 +2561,7 @@ int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); * * @return Returns 0 on success. */ -int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); +int xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t num, FILE *fp); /** * @brief Print formatted string to \a fp. @@ -2559,7 +2575,20 @@ int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); * * @return Returns 0 on success. */ -int xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp); +int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp); + +/** + * @brief Print formatted ::xbps_object_t to \a fp. + * + * Prints the ::xbps_object_t \a obj to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] obj The object to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp); /**@}*/ diff --git a/lib/format.c b/lib/format.c index 0db817ae5..dc34be670 100644 --- a/lib/format.c +++ b/lib/format.c @@ -101,35 +101,12 @@ strbuf_release(struct strbuf *sb) sb->len = sb->sz = 0; } -struct common { - int type; -}; - -struct chunk { - struct common common; - char s[]; -}; - -struct var { - struct common common; - struct xbps_fmt_spec spec; - char s[]; -}; - -struct xbps_fmt { - union { - struct common *common; - struct chunk *chunk; - struct var *var; - }; -}; - -enum { +enum tok { TTEXT = 1, TVAR, }; -static int +static enum tok nexttok(const char **pos, struct strbuf *buf) { const char *p; @@ -182,7 +159,7 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } -struct conversion { +struct xbps_fmt_conv { enum { HUMANIZE = 1, STRMODE } type; union { struct humanize { @@ -258,31 +235,54 @@ parse_humanize(const char **pos, struct humanize *humanize) } static int -parse_conversion(const char **pos, struct conversion *conversion) +parse_conversion(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_conv *conv_storage) { - if (**pos != '!') + if (**pos != '!') { + fmt->conv = NULL; return 0; + } + fmt->conv = conv_storage; + if (!conv_storage) + fmt->conv = calloc(1, sizeof(*fmt->conv)); + if (!fmt->conv) + return -errno; if (strncmp(*pos + 1, "strmode", sizeof("strmode") - 1) == 0) { *pos += sizeof("strmode"); - conversion->type = STRMODE; + fmt->conv->type = STRMODE; return 0; } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { - conversion->type = HUMANIZE; + fmt->conv->type = HUMANIZE; *pos += sizeof("humanize"); - return parse_humanize(pos, &conversion->humanize); + return parse_humanize(pos, &fmt->conv->humanize); } return -EINVAL; } static int -parse_spec(const char **pos, struct xbps_fmt_spec *spec) +parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_storage) { bool fill = false; + struct xbps_fmt_spec *spec; const char *p = *pos; int r; + /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ + + if (*p != ':') { + fmt->spec = NULL; + return 0; + } + p++; + + if (!spec_storage) { + spec = fmt->spec = calloc(1, sizeof(*fmt->spec)); + if (!fmt->spec) + return -errno; + } else { + spec = fmt->spec = spec_storage; + } + /* defaults */ - spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; spec->sign = '-'; @@ -290,12 +290,6 @@ parse_spec(const char **pos, struct xbps_fmt_spec *spec) spec->precision = 0; spec->type = '\0'; - /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ - - if (*p != ':') - return 0; - p++; - /* fill ::= . */ if (*p && strchr("<>=", p[1])) { fill = true; @@ -350,7 +344,10 @@ parse_spec(const char **pos, struct xbps_fmt_spec *spec) } static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +parse(const char **pos, struct xbps_fmt *fmt, + struct strbuf *buf, + struct xbps_fmt_conv *conv_storage, + struct xbps_fmt_spec *spec_storage) { const char *p = *pos; const char *e; @@ -371,19 +368,25 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c if (e == p) return -EINVAL; - strbuf_reset(buf); - r = strbuf_puts(buf, p, e - p); - if (r < 0) - return r; + if (buf) { + strbuf_reset(buf); + r = strbuf_puts(buf, p, e - p); + if (r < 0) + return r; + } else { + fmt->var = strndup(p, e - p); + if (!fmt->var) + return -errno; + } p = e; /* conversion ::= ['!' ...] */ - r = parse_conversion(&p, conversion); + r = parse_conversion(&p, fmt, conv_storage); if (r < 0) return r; /* format_spec ::= [':' ...] */ - r = parse_spec(&p, spec); + r = parse_spec(&p, fmt, spec_storage); if (r < 0) return r; @@ -403,45 +406,31 @@ xbps_fmt_parse(const char *format) int r = 1; for (;;) { - struct xbps_fmt_spec spec = {0}; - struct conversion conversion = {0}; struct xbps_fmt *tmp; - r = nexttok(&pos, &buf); - if (r < 0) - goto err; + enum tok t; + + t = nexttok(&pos, &buf); + tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); if (!tmp) goto err_errno; fmt = tmp; - switch (r) { - case 0: - fmt[n].common = NULL; + memset(&fmt[n], '\0', sizeof(struct xbps_fmt)); + + if (t == 0) goto out; - case TTEXT: - fmt[n].chunk = calloc(1, sizeof(struct chunk)+buf.len+1); - fmt[n].common->type = TTEXT; - if (!fmt[n].chunk) + if (t == TTEXT) { + fmt[n].prefix = strndup(buf.mem, buf.len); + if (!fmt[n].prefix) goto err_errno; - memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); - break; - case TVAR: - r = parse(&pos, &buf, &spec, &conversion); + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + r = parse(&pos, &fmt[n], NULL, NULL, NULL); if (r < 0) goto err; - fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); - if (!fmt[n].var) - goto err_errno; - fmt[n].common->type = TVAR; - fmt[n].var->spec = spec; - memcpy(fmt[n].var->s, buf.mem, buf.len+1); - if (conversion.type) { - fmt[n].var->spec.conversion = calloc(1, sizeof(struct conversion)); - if (!fmt[n].var->spec.conversion) - goto err_errno; - *fmt[n].var->spec.conversion = conversion; - } - break; } + fprintf(stderr, "fmt: prefix='%s' var='%s'\n", fmt[n].prefix, fmt[n].var); n++; } out: @@ -461,14 +450,12 @@ xbps_fmt_free(struct xbps_fmt *fmt) { if (!fmt) return; - for (struct xbps_fmt *f = fmt; f->common; f++) - switch (f->common->type) { - case TTEXT: free(f->chunk); break; - case TVAR: - free(f->var->spec.conversion); - free(f->var); - break; - } + for (struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + free(f->prefix); + free(f->var); + free(f->spec); + free(f->conv); + } free(fmt); } @@ -480,23 +467,25 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) int r = 0; for (;;) { - struct xbps_fmt_spec spec = {0}; - struct conversion conversion = {0}; - r = nexttok(&pos, &buf); - if (r <= 0) + enum tok t; + + t = nexttok(&pos, &buf); + if (t == 0) goto out; - switch (r) { - case TTEXT: + if (t == TTEXT) { fprintf(fp, "%s", buf.mem); - break; - case TVAR: - r = parse(&pos, &buf, &spec, &conversion); + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + struct xbps_fmt_spec spec = {0}; + struct xbps_fmt_conv conv = {0}; + struct xbps_fmt fmt = { .var = buf.mem }; + r = parse(&pos, &fmt, &buf, &conv, &spec); if (r < 0) goto out; - r = cb(fp, &spec, buf.mem, data); + r = cb(fp, &fmt, data); if (r != 0) goto out; - break; } } out: @@ -508,16 +497,13 @@ int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) { int r; - for (const struct xbps_fmt *f = fmt; f->common; f++) { - switch (f->common->type) { - case TTEXT: - fprintf(fp, "%s", f->chunk->s); - break; - case TVAR: - r = cb(fp, &f->var->spec, f->var->s, data); + for (const struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + if (f->prefix) + fprintf(fp, "%s", f->prefix); + if (f->var) { + r = cb(fp, f, data); if (r != 0) return r; - break; } } return 0; @@ -528,16 +514,17 @@ struct fmt_dict_cb { }; int -xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp) +xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp) { + const struct xbps_fmt_spec *spec = fmt->spec; if (len == 0) len = strlen(str); - if (spec->align == '>' && spec->width > (unsigned)len) { + if (spec && spec->align == '>' && spec->width > (unsigned)len) { for (unsigned i = 0; i < spec->width - len; i++) fputc(spec->fill, fp); } fprintf(fp, "%.*s", (int)len, str); - if (spec->align == '<' && spec->width > (unsigned)len) { + if (spec && spec->align == '<' && spec->width > (unsigned)len) { for (unsigned i = 0; i < spec->width - len; i++) fputc(spec->fill, fp); } @@ -545,10 +532,9 @@ xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, F } static int -humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE *fp) { char buf[64]; - const struct humanize *h = &spec->conversion->humanize; int scale = 0; int width = h->width ? h->width : 8; int len; @@ -567,41 +553,45 @@ humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) len = humanize_number(buf, width, d, "B", scale, h->flags); if (len == -1) return -EINVAL; - return xbps_fmt_string(spec, buf, len, fp); + return xbps_fmt_print_string(fmt, buf, len, fp); } static int -tostrmode(const struct xbps_fmt_spec *spec UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +tostrmode(const struct xbps_fmt *fmt UNUSED, int64_t d UNUSED, FILE *fp UNUSED) { return -ENOTSUP; } int -xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) { char buf[64]; - struct xbps_fmt_spec strspec = *spec; + struct xbps_fmt_spec strspec = {0}; + struct xbps_fmt strfmt = { .spec = &strspec }; + struct xbps_fmt_spec *spec = fmt->spec; const char *p = buf; int len; - if (spec->conversion) { - switch (spec->conversion->type) { - case HUMANIZE: return humanize(spec, d, fp); - case STRMODE: return tostrmode(spec, d, fp); + if (fmt->conv) { + switch (fmt->conv->type) { + case HUMANIZE: return humanize(&fmt->conv->humanize, fmt, d, fp); + case STRMODE: return tostrmode(fmt, d, fp); } } + if (spec) { + strspec = *spec; + if (spec->align == '=') + strspec.align = '>'; + } - if (strspec.align == '=') - strspec.align = '>'; - - switch (spec->type) { + switch (spec ? spec->type : '\0') { default: /* fallthrough */ case 'd': - if (spec->sign == '+') + if (spec && spec->sign == '+') len = snprintf(buf, sizeof(buf), "%+" PRId64, d); else len = snprintf(buf, sizeof(buf), "%" PRId64, d); - if (spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { + if (spec && spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { len--, p++; strspec.width -= 1; fputc(buf[0], fp); @@ -612,27 +602,36 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; case 'X': len = snprintf(buf, sizeof(buf), "%" PRIX64, d); break; } - return xbps_fmt_string(&strspec, p, len, fp); + return xbps_fmt_print_string(&strfmt, p, len, fp); } -static int -fmt_dict_cb(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data) +int +xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) { - struct fmt_dict_cb *ctx = data; - xbps_object_t val = xbps_dictionary_get(ctx->dict, var); - - switch (xbps_object_type(val)) { - case XBPS_TYPE_STRING: - return xbps_fmt_string(spec, xbps_string_cstring_nocopy(val), - xbps_string_size(val), fp); + switch (xbps_object_type(obj)) { + case XBPS_TYPE_BOOL: + return xbps_fmt_print_string(fmt, xbps_bool_true(obj) ? "true" : "false", 0, fp); case XBPS_TYPE_NUMBER: - return xbps_fmt_number(spec, xbps_number_integer_value(val), fp); + return xbps_fmt_print_number(fmt, xbps_number_integer_value(obj), fp); + case XBPS_TYPE_STRING: + return xbps_fmt_print_string(fmt, xbps_string_cstring_nocopy(obj), + xbps_string_size(obj), fp); + case XBPS_TYPE_UNKNOWN: + return xbps_fmt_print_string(fmt, "(null)", 0, fp); default: break; } return 0; } +static int +fmt_dict_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) +{ + struct fmt_dict_cb *ctx = data; + xbps_object_t obj = xbps_dictionary_get(ctx->dict, fmt->var); + return xbps_fmt_print_object(fmt, obj, fp); +} + int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp) { diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c index 38b98c723..381f2e911 100644 --- a/tests/xbps/libxbps/fmt/main.c +++ b/tests/xbps/libxbps/fmt/main.c @@ -35,14 +35,14 @@ #include #include -ATF_TC(xbps_fmt_number); +ATF_TC(xbps_fmt_print_number); -ATF_TC_HEAD(xbps_fmt_number, tc) +ATF_TC_HEAD(xbps_fmt_print_number, tc) { - atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_number"); + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_number"); } -ATF_TC_BODY(xbps_fmt_number, tc) +ATF_TC_BODY(xbps_fmt_print_number, tc) { char *buf = NULL; size_t bufsz = 0; @@ -71,9 +71,10 @@ ATF_TC_BODY(xbps_fmt_number, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_number(&tests[i].spec, tests[i].d, fp); + xbps_fmt_print_number(&fmt, tests[i].d, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } @@ -81,14 +82,14 @@ ATF_TC_BODY(xbps_fmt_number, tc) free(buf); } -ATF_TC(xbps_fmt_string); +ATF_TC(xbps_fmt_print_string); -ATF_TC_HEAD(xbps_fmt_string, tc) +ATF_TC_HEAD(xbps_fmt_print_string, tc) { - atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_string"); + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_string"); } -ATF_TC_BODY(xbps_fmt_string, tc) +ATF_TC_BODY(xbps_fmt_print_string, tc) { char *buf = NULL; size_t bufsz = 0; @@ -108,9 +109,10 @@ ATF_TC_BODY(xbps_fmt_string, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_string(&tests[i].spec, tests[i].input, tests[i].len, fp); + xbps_fmt_print_string(&fmt, tests[i].input, tests[i].len, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } @@ -136,10 +138,10 @@ ATF_TC_BODY(xbps_fmt_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number}<")); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize}<")); ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); @@ -162,9 +164,9 @@ ATF_TC_BODY(xbps_fmts_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number}<", dict, fp) == 0); + ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number} {number!humanize}<", dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); @@ -172,8 +174,8 @@ ATF_TC_BODY(xbps_fmts_dictionary, tc) ATF_TP_ADD_TCS(tp) { - ATF_TP_ADD_TC(tp, xbps_fmt_number); - ATF_TP_ADD_TC(tp, xbps_fmt_string); + ATF_TP_ADD_TC(tp, xbps_fmt_print_number); + ATF_TP_ADD_TC(tp, xbps_fmt_print_string); ATF_TP_ADD_TC(tp, xbps_fmt_dictionary); ATF_TP_ADD_TC(tp, xbps_fmts_dictionary); return atf_no_error(); From eccfbeb1e2c3fb08f8fdc14a51616c0078473e9a Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 18:17:47 +0100 Subject: [PATCH 13/34] lib/format.c: add optional default to format string variables --- bin/xbps-query/xbps-query.1 | 15 +-- include/xbps.h.in | 26 +++++- lib/format.c | 167 ++++++++++++++++++++++++++++++---- tests/xbps/libxbps/fmt/main.c | 4 +- 4 files changed, 184 insertions(+), 28 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 9aa6a8cc1..3db83d686 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -301,14 +301,17 @@ xbps 484 KB .Pp Format strings are parsed by the following EBNF: .Bd -literal - ::= (text | escape | substitution)* - ::= [^\\{}]+ -- literal text chunk - ::= "\\" [abfnrtv0] -- POSIX-like espace sequence - | "\\{" | "\\}" -- escaped "{" and "}" + ::= (prefix | "\\" (escape|[{}]) | substitution)* + ::= [^\\{}]+ -- Literal text chunk. + ::= [abfnrtv0] -- POSIX-like espace character. - ::= "{" variable ["!" conversion] [":" format] "}" + ::= "{" variable ["?" default] ["!" conversion] [":" format] "}" ::= [a-zA-Z0-9_-] + ::= ([-]?[0-9]+) -- default number. + | "true" | "false" -- default boolean. + | ('"' (("\\" (escape|'"')) | [^"])* '"') -- default string. + ::= humanize | strmode -- Convert inode status information into a symbolic string @@ -329,7 +332,7 @@ Format strings are parsed by the following EBNF: | "T" -- tera | "P" -- peta | "E" -- exa - ::= "i" -- Use IEE/IEC (and now also SI) power of two prefixes. + ::= "i" -- Use IEEE/IEC (and now also SI) power of two prefixes. ::= [[fill] align] [sign] [width] ["." precision] [type] ::= -- The character to use when aligning the output. diff --git a/include/xbps.h.in b/include/xbps.h.in index a89e3d415..2566b3443 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2375,7 +2375,7 @@ xbps_plist_dictionary_from_file(const char *path); /** * @struct xbps_fmt xbps.h "xbps.h" - * @brief Structure of parsed format string. + * @brief Structure of parsed format string variable. */ struct xbps_fmt { /** @@ -2389,6 +2389,11 @@ struct xbps_fmt { * @brief Variable name. */ char *var; + /** + * @var def + * @brief Default value. + */ + struct xbps_fmt_def *def; /** * @var conv * @brief Format conversion. @@ -2402,7 +2407,24 @@ struct xbps_fmt { }; /** - * @struct xbps_fmt xbps.h "xbps.h" + * @struct xbps_fmt_def xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_def { + enum { + XBPS_FMT_DEF_STR = 1, + XBPS_FMT_DEF_NUM, + XBPS_FMT_DEF_BOOL, + } type; + union { + char *str; + int64_t num; + bool boolean; + } val; +}; + +/** + * @struct xbps_fmt_spec xbps.h "xbps.h" * @brief Structure of parsed format specifier. */ struct xbps_fmt_spec { diff --git a/lib/format.c b/lib/format.c index dc34be670..4cea7e0a4 100644 --- a/lib/format.c +++ b/lib/format.c @@ -159,19 +159,6 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } -struct xbps_fmt_conv { - enum { HUMANIZE = 1, STRMODE } type; - union { - struct humanize { - unsigned width : 8; - unsigned minscale : 8; - unsigned maxscale : 8; - bool decimal : 1; - int flags; - } humanize; - }; -}; - static int parse_u(const char **pos, unsigned int *u) { @@ -188,6 +175,129 @@ parse_u(const char **pos, unsigned int *u) return 0; } +static int +parse_d(const char **pos, int64_t *d) +{ + char *e = NULL; + long v; + errno = 0; + v = strtol(*pos, &e, 10); + if (errno != 0) + return -errno; + if (v > UINT_MAX) + return -ERANGE; + *d = v; + *pos = e; + return 0; +} + +static int +parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, + struct xbps_fmt_def *def_storage) +{ + struct strbuf buf2 = {0}; + struct xbps_fmt_def *def; + const char *p = *pos; + char *str = NULL; + int r; + + if (*p++ != '?') + return 0; + if (!def_storage) { + fmt->def = def = calloc(1, sizeof(*def)); + if (!def) + return -errno; + } else { + fmt->def = def = def_storage; + } + + if ((*p >= '0' && *p <= '9') || *p == '-') { + r = parse_d(&p, &def->val.num); + if (r < 0) + return r; + def->type = XBPS_FMT_DEF_NUM; + *pos = p; + return 0; + } else if (strncmp(p, "true", sizeof("true") - 1) == 0) { + *pos = p + sizeof("true") - 1; + def->type = XBPS_FMT_DEF_BOOL; + def->val.boolean = true; + return 0; + } else if (strncmp(p, "false", sizeof("false") - 1) == 0) { + *pos = p + sizeof("false") - 1; + def->type = XBPS_FMT_DEF_BOOL; + def->val.boolean = false; + return 0; + } + + if (*p++ != '"') + return -EINVAL; + + if (!buf) { + buf = &buf2; + } else { + r = strbuf_putc(buf, '\0'); + if (r < 0) + return r; + str = buf->mem + buf->len; + } + for (; *p && *p != '"'; p++) { + switch (*p) { + case '\\': + switch (*++p) { + case '\\': r = strbuf_putc(buf, '\\'); break; + case 'a': r = strbuf_putc(buf, '\a'); break; + case 'b': r = strbuf_putc(buf, '\b'); break; + case 'f': r = strbuf_putc(buf, '\f'); break; + case 'n': r = strbuf_putc(buf, '\n'); break; + case 'r': r = strbuf_putc(buf, '\r'); break; + case 't': r = strbuf_putc(buf, '\t'); break; + case '0': r = strbuf_putc(buf, '\0'); break; + case '"': r = strbuf_putc(buf, '"'); break; + default: r = -EINVAL; + } + break; + default: + r = strbuf_putc(buf, *p); + } + if (r < 0) + goto err; + } + if (*p++ != '"') { + r = -EINVAL; + goto err; + } + *pos = p; + def->type = XBPS_FMT_DEF_STR; + if (buf == &buf2) { + def->val.str = strdup(buf2.mem); + if (!def->val.str) { + r = -errno; + goto err; + } + strbuf_release(&buf2); + } else { + def->val.str = str; + } + return 0; +err: + strbuf_release(&buf2); + return r; +} + +struct xbps_fmt_conv { + enum { HUMANIZE = 1, STRMODE } type; + union { + struct humanize { + unsigned width : 8; + unsigned minscale : 8; + unsigned maxscale : 8; + bool decimal : 1; + int flags; + } humanize; + }; +}; + static int parse_humanize(const char **pos, struct humanize *humanize) { @@ -346,6 +456,7 @@ parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_st static int parse(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, + struct xbps_fmt_def *def_storage, struct xbps_fmt_conv *conv_storage, struct xbps_fmt_spec *spec_storage) { @@ -357,7 +468,7 @@ parse(const char **pos, struct xbps_fmt *fmt, return -EINVAL; p++; - /* var ::= '{' name [conversion][format_spec] '}' */ + /* var ::= '{' name [default][conversion][format_spec] '}' */ /* name ::= [a-zA-Z0-9_-]+ */ for (e = p; (*e >= 'a' && *e <= 'z') || @@ -380,6 +491,11 @@ parse(const char **pos, struct xbps_fmt *fmt, } p = e; + /* default ::= ['?' ...] */ + r = parse_default(&p, fmt, buf, def_storage); + if (r < 0) + return r; + /* conversion ::= ['!' ...] */ r = parse_conversion(&p, fmt, conv_storage); if (r < 0) @@ -426,7 +542,7 @@ xbps_fmt_parse(const char *format) t = nexttok(&pos, &buf); } if (t == TVAR) { - r = parse(&pos, &fmt[n], NULL, NULL, NULL); + r = parse(&pos, &fmt[n], NULL, NULL, NULL, NULL); if (r < 0) goto err; } @@ -453,6 +569,9 @@ xbps_fmt_free(struct xbps_fmt *fmt) for (struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { free(f->prefix); free(f->var); + if (f->def && f->def->type == XBPS_FMT_DEF_STR) + free(f->def->val.str); + free(f->def); free(f->spec); free(f->conv); } @@ -477,10 +596,11 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) t = nexttok(&pos, &buf); } if (t == TVAR) { - struct xbps_fmt_spec spec = {0}; + struct xbps_fmt_def def = {0}; struct xbps_fmt_conv conv = {0}; + struct xbps_fmt_spec spec = {0}; struct xbps_fmt fmt = { .var = buf.mem }; - r = parse(&pos, &fmt, &buf, &conv, &spec); + r = parse(&pos, &fmt, &buf, &def, &conv, &spec); if (r < 0) goto out; r = cb(fp, &fmt, data); @@ -617,7 +737,18 @@ xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) return xbps_fmt_print_string(fmt, xbps_string_cstring_nocopy(obj), xbps_string_size(obj), fp); case XBPS_TYPE_UNKNOWN: - return xbps_fmt_print_string(fmt, "(null)", 0, fp); + if (fmt->def) { + struct xbps_fmt_def *def = fmt->def; + switch (fmt->def->type) { + case XBPS_FMT_DEF_BOOL: + return xbps_fmt_print_string(fmt, def->val.boolean ? + "true" : "false", 0, fp); + case XBPS_FMT_DEF_STR: + return xbps_fmt_print_string(fmt, def->val.str, 0, fp); + case XBPS_FMT_DEF_NUM: + return xbps_fmt_print_number(fmt, def->val.num, fp); + } + } default: break; } diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c index 381f2e911..40298a98e 100644 --- a/tests/xbps/libxbps/fmt/main.c +++ b/tests/xbps/libxbps/fmt/main.c @@ -138,10 +138,10 @@ ATF_TC_BODY(xbps_fmt_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize}<")); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize} {foo?\"bar\"} {n?1000!humanize}<")); ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1 0KB<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB bar 1KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); From cd41df823f6ae7ab65bff8869614cee554dff9cf Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 18:49:27 +0100 Subject: [PATCH 14/34] lib/format.c: simplify escape characters --- lib/format.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/format.c b/lib/format.c index 4cea7e0a4..462fa28c3 100644 --- a/lib/format.c +++ b/lib/format.c @@ -133,17 +133,12 @@ nexttok(const char **pos, struct strbuf *buf) case 'r': r = strbuf_putc(buf, '\r'); break; case 't': r = strbuf_putc(buf, '\t'); break; case '0': r = strbuf_putc(buf, '\0'); break; - case '{': r = strbuf_putc(buf, '{'); break; - case '}': r = strbuf_putc(buf, '}'); break; - default: - r = strbuf_putc(buf, '\\'); - if (r < 0) - break; - r = strbuf_putc(buf, *p); + default: r = *p ? strbuf_putc(buf, *p) : 0; } if (r < 0) return r; - p++; + if (*p) + p++; break; default: r = strbuf_putc(buf, *p++); @@ -241,9 +236,8 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, return r; str = buf->mem + buf->len; } - for (; *p && *p != '"'; p++) { - switch (*p) { - case '\\': + for (; *p && *p != '"';) { + if (*p == '\\') { switch (*++p) { case '\\': r = strbuf_putc(buf, '\\'); break; case 'a': r = strbuf_putc(buf, '\a'); break; @@ -253,15 +247,15 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, case 'r': r = strbuf_putc(buf, '\r'); break; case 't': r = strbuf_putc(buf, '\t'); break; case '0': r = strbuf_putc(buf, '\0'); break; - case '"': r = strbuf_putc(buf, '"'); break; - default: r = -EINVAL; + default: r = *p ? strbuf_putc(buf, *p) : 0; } - break; - default: + } else { r = strbuf_putc(buf, *p); } if (r < 0) goto err; + if (*p) + p++; } if (*p++ != '"') { r = -EINVAL; From d3242633d696e85d1dd57c129a3bcfaf249db13b Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 20:05:28 +0100 Subject: [PATCH 15/34] lib/format.c: fix parsing empty default string --- lib/format.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/format.c b/lib/format.c index 462fa28c3..6e00e39e1 100644 --- a/lib/format.c +++ b/lib/format.c @@ -264,7 +264,7 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, *pos = p; def->type = XBPS_FMT_DEF_STR; if (buf == &buf2) { - def->val.str = strdup(buf2.mem); + def->val.str = strdup(buf2.mem ? buf2.mem : ""); if (!def->val.str) { r = -errno; goto err; From 8b0663b8d3af27029006008a658dbdad27b304a0 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 15 Mar 2023 00:45:20 +0100 Subject: [PATCH 16/34] lib/format.c: fix xbps_fmts* --- lib/format.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/format.c b/lib/format.c index 6e00e39e1..561ffcbed 100644 --- a/lib/format.c +++ b/lib/format.c @@ -478,6 +478,7 @@ parse(const char **pos, struct xbps_fmt *fmt, r = strbuf_puts(buf, p, e - p); if (r < 0) return r; + fmt->var = buf->mem; } else { fmt->var = strndup(p, e - p); if (!fmt->var) From 902ad16f74717c10ce13b9d415ed25ca86fb126e Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 19 Jun 2023 19:37:43 +0200 Subject: [PATCH 17/34] lib/format.c: handle errors from nexttok --- lib/format.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/format.c b/lib/format.c index 561ffcbed..7900190dc 100644 --- a/lib/format.c +++ b/lib/format.c @@ -106,8 +106,8 @@ enum tok { TVAR, }; -static enum tok -nexttok(const char **pos, struct strbuf *buf) +static int +nexttok(enum tok *tok, const char **pos, struct strbuf *buf) { const char *p; int r; @@ -121,8 +121,10 @@ nexttok(const char **pos, struct strbuf *buf) case '{': *pos = p; if (buf->len > 0) - return TTEXT; - return TVAR; + *tok = TTEXT; + else + *tok = TVAR; + return 1; case '\\': switch (*++p) { case '\\': r = strbuf_putc(buf, '\\'); break; @@ -148,7 +150,8 @@ nexttok(const char **pos, struct strbuf *buf) } if (buf->len > 0) { *pos = p; - return TTEXT; + *tok = TTEXT; + return 1; } p++; return 0; @@ -520,7 +523,9 @@ xbps_fmt_parse(const char *format) struct xbps_fmt *tmp; enum tok t; - t = nexttok(&pos, &buf); + r = nexttok(&t, &pos, &buf); + if (r < 0) + goto err; tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); if (!tmp) @@ -528,13 +533,15 @@ xbps_fmt_parse(const char *format) fmt = tmp; memset(&fmt[n], '\0', sizeof(struct xbps_fmt)); - if (t == 0) + if (r == 0) goto out; if (t == TTEXT) { fmt[n].prefix = strndup(buf.mem, buf.len); if (!fmt[n].prefix) goto err_errno; - t = nexttok(&pos, &buf); + r = nexttok(&t, &pos, &buf); + if (r < 0) + goto err; } if (t == TVAR) { r = parse(&pos, &fmt[n], NULL, NULL, NULL, NULL); @@ -583,12 +590,14 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) for (;;) { enum tok t; - t = nexttok(&pos, &buf); - if (t == 0) + r = nexttok(&t, &pos, &buf); + if (r <= 0) goto out; if (t == TTEXT) { fprintf(fp, "%s", buf.mem); - t = nexttok(&pos, &buf); + r = nexttok(&t, &pos, &buf); + if (r <= 0) + goto out; } if (t == TVAR) { struct xbps_fmt_def def = {0}; From 5dfadfad2f6244d26709fdae9bb486903ecbd411 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 18 Sep 2023 22:38:37 +0200 Subject: [PATCH 18/34] lib/json.c: add json printing stuff --- include/xbps/json.h | 34 ++++++ lib/Makefile | 2 +- lib/json.c | 203 +++++++++++++++++++++++++++++++ tests/xbps/libxbps/Kyuafile | 1 + tests/xbps/libxbps/Makefile | 1 + tests/xbps/libxbps/json/Kyuafile | 5 + tests/xbps/libxbps/json/Makefile | 8 ++ tests/xbps/libxbps/json/main.c | 155 +++++++++++++++++++++++ 8 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 include/xbps/json.h create mode 100644 lib/json.c create mode 100644 tests/xbps/libxbps/json/Kyuafile create mode 100644 tests/xbps/libxbps/json/Makefile create mode 100644 tests/xbps/libxbps/json/main.c diff --git a/include/xbps/json.h b/include/xbps/json.h new file mode 100644 index 000000000..1264d3836 --- /dev/null +++ b/include/xbps/json.h @@ -0,0 +1,34 @@ +/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck */ +/* SPDX-License-Identifier: BSD-2-Clause */ +#ifndef _XBPS_JSON_H_ +#define _XBPS_JSON_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct xbps_json_printer { + FILE *file; + unsigned depth; + uint8_t indent; + bool compact; +}; + +int xbps_json_print_escape(struct xbps_json_printer *p, const char *s); +int xbps_json_print_quote(struct xbps_json_printer *p, const char *s); +int xbps_json_print_bool(struct xbps_json_printer *p, bool b); + +int xbps_json_print_xbps_string(struct xbps_json_printer *p, xbps_string_t str); +int xbps_json_print_xbps_number(struct xbps_json_printer *p, xbps_number_t num); +int xbps_json_print_xbps_boolean(struct xbps_json_printer *p, xbps_bool_t b); +int xbps_json_print_xbps_array(struct xbps_json_printer *p, xbps_array_t array); +int xbps_json_print_xbps_dictionary(struct xbps_json_printer *p, xbps_dictionary_t dict); +int xbps_json_print_xbps_object(struct xbps_json_printer *p, xbps_object_t obj); + +#endif /* !_XBPS_JSON_H_ */ diff --git a/lib/Makefile b/lib/Makefile index bd9838570..db3b2bdf9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -51,7 +51,7 @@ OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o OBJS += repo.o repo_sync.o OBJS += rpool.o cb_util.o proplib_wrapper.o OBJS += package_alternatives.o -OBJS += conf.o log.o format.o +OBJS += conf.o log.o format.o json.o OBJS += $(EXTOBJS) $(COMPAT_OBJS) # unnecessary unless pkgdb format changes # OBJS += pkgdb_conversion.o diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 000000000..75e7d0af9 --- /dev/null +++ b/lib/json.c @@ -0,0 +1,203 @@ +/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck */ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "xbps/xbps_array.h" +#include "xbps/xbps_bool.h" +#include "xbps/xbps_dictionary.h" +#include "xbps/xbps_number.h" +#include "xbps/xbps_object.h" +#include "xbps/xbps_string.h" + +#include "xbps/json.h" + +static int __attribute__ ((format (printf, 2, 3))) +xbps_json_printf(struct xbps_json_printer *p, const char *fmt, ...) +{ + va_list ap; + int r = 0; + va_start(ap, fmt); + if (vfprintf(p->file, fmt, ap) < 0) + r = errno ? -errno : -EIO; + va_end(ap); + return r; +} + +int +xbps_json_print_escape(struct xbps_json_printer *p, const char *s) +{ + int r = 0; + for (; r >= 0 && *s; s++) { + switch (*s) { + case '"': r = xbps_json_printf(p, "\\\""); break; + case '\\': r = xbps_json_printf(p, "\\\\"); break; + case '\b': r = xbps_json_printf(p, "\\b"); break; + case '\f': r = xbps_json_printf(p, "\\f"); break; + case '\n': r = xbps_json_printf(p, "\\n"); break; + case '\r': r = xbps_json_printf(p, "\\r"); break; + case '\t': r = xbps_json_printf(p, "\\t"); break; + default: + if ((unsigned)*s < 0x20) { + r = xbps_json_printf(p, "\\u%04x", *s); + } else { + r = xbps_json_printf(p, "%c", *s); + } + } + } + return r; +} + +int +xbps_json_print_quote(struct xbps_json_printer *p, const char *s) +{ + int r; + if ((r = xbps_json_printf(p, "\"")) < 0) + return r; + if ((r = xbps_json_print_escape(p, s)) < 0) + return r; + return xbps_json_printf(p, "\""); +} + +int +xbps_json_print_bool(struct xbps_json_printer *p, bool b) +{ + return xbps_json_printf(p, b ? "true" : "false"); +} + +int +xbps_json_print_xbps_string(struct xbps_json_printer *p, xbps_string_t str) +{ + return xbps_json_print_quote(p, xbps_string_cstring_nocopy(str)); +} + +int +xbps_json_print_xbps_number(struct xbps_json_printer *p, xbps_number_t num) +{ + if (xbps_number_unsigned(num)) { + return xbps_json_printf(p, "%" PRIu64, xbps_number_unsigned_integer_value(num)); + } else { + return xbps_json_printf(p, "%" PRId64, xbps_number_integer_value(num)); + } + return 0; +} + +int +xbps_json_print_xbps_boolean(struct xbps_json_printer *p, xbps_bool_t b) +{ + return xbps_json_print_bool(p, xbps_bool_true(b)); +} + +int +xbps_json_print_xbps_array(struct xbps_json_printer *p, xbps_array_t array) +{ + const char *item_sep = p->compact ? "," : ", "; + int indent = 0; + unsigned i = 0; + int r; + p->depth++; + if (!p->compact && p->indent > 0) { + indent = p->indent*p->depth; + item_sep = ",\n"; + } + if ((r = xbps_json_printf(p, "[")) < 0) + return r; + for (; i < xbps_array_count(array); i++) { + if (i == 0) { + if (indent > 0 && (r = xbps_json_printf(p, "\n%*s", indent, "")) < 0) + return r; + } else if ((r = xbps_json_printf(p, "%s%*s", item_sep, indent, "")) < 0) { + return r; + } + if ((r = xbps_json_print_xbps_object(p, xbps_array_get(array, i))) < 0) + return r; + } + + p->depth--; + if (indent > 0 && i > 0) + return xbps_json_printf(p, "\n%*s]", p->indent*p->depth, ""); + return xbps_json_printf(p, "]"); +} + +int +xbps_json_print_xbps_dictionary(struct xbps_json_printer *p, xbps_dictionary_t dict) +{ + xbps_object_iterator_t iter; + xbps_object_t keysym; + const char *item_sep = p->compact ? "," : ", "; + const char *key_sep = p->compact ? ":": ": "; + bool first = true; + int indent = 0; + int r; + + iter = xbps_dictionary_iterator(dict); + if (!iter) + return errno ? -errno : -ENOMEM; + + p->depth++; + if (!p->compact && p->indent > 0) { + indent = p->depth*p->indent; + item_sep = ",\n"; + } + + if ((r = xbps_json_printf(p, "{")) < 0) + goto err; + + while ((keysym = xbps_object_iterator_next(iter))) { + xbps_object_t obj; + const char *key; + + if (first) { + first = false; + if (p->indent > 0 && (r = xbps_json_printf(p, "\n%*s", indent, "")) < 0) { + goto err; + } + } else if ((r = xbps_json_printf(p, "%s%*s", item_sep, indent, "")) < 0) { + goto err; + } + + key = xbps_dictionary_keysym_cstring_nocopy(keysym); + if ((r = xbps_json_print_quote(p, key)) < 0) + goto err; + if ((r = xbps_json_printf(p, "%s", key_sep)) < 0) + goto err; + + obj = xbps_dictionary_get_keysym(dict, keysym); + if ((r = xbps_json_print_xbps_object(p, obj)) < 0) + goto err; + } + + xbps_object_iterator_release(iter); + p->depth--; + if (indent > 0 && !first) + return xbps_json_printf(p, "\n%*s}", p->indent*p->depth, ""); + return xbps_json_printf(p, "}"); + +err: + xbps_object_iterator_release(iter); + return r; +} + +int +xbps_json_print_xbps_object(struct xbps_json_printer *p, xbps_object_t obj) +{ + if (!obj) return xbps_json_printf(p, "null"); + switch (xbps_object_type(obj)) { + case XBPS_TYPE_ARRAY: return xbps_json_print_xbps_array(p, obj); + case XBPS_TYPE_BOOL: return xbps_json_print_xbps_boolean(p, obj); + case XBPS_TYPE_DATA: return xbps_json_printf(p, "true"); + case XBPS_TYPE_DICTIONARY: return xbps_json_print_xbps_dictionary(p, obj); + case XBPS_TYPE_DICT_KEYSYM: return -EINVAL; + case XBPS_TYPE_NUMBER: return xbps_json_print_xbps_number(p, obj); + case XBPS_TYPE_STRING: return xbps_json_print_xbps_string(p, obj); + case XBPS_TYPE_UNKNOWN: return -EINVAL; + } + return -EINVAL; +} diff --git a/tests/xbps/libxbps/Kyuafile b/tests/xbps/libxbps/Kyuafile index 050e80733..e0fa03a83 100644 --- a/tests/xbps/libxbps/Kyuafile +++ b/tests/xbps/libxbps/Kyuafile @@ -13,3 +13,4 @@ include('find_pkg_orphans/Kyuafile') include('pkgdb/Kyuafile') include('shell/Kyuafile') include('fmt/Kyuafile') +include('json/Kyuafile') diff --git a/tests/xbps/libxbps/Makefile b/tests/xbps/libxbps/Makefile index 17bcd132e..ca884a459 100644 --- a/tests/xbps/libxbps/Makefile +++ b/tests/xbps/libxbps/Makefile @@ -13,5 +13,6 @@ SUBDIRS += pkgdb SUBDIRS += config SUBDIRS += shell SUBDIRS += fmt +SUBDIRS += json include ../../../mk/subdir.mk diff --git a/tests/xbps/libxbps/json/Kyuafile b/tests/xbps/libxbps/json/Kyuafile new file mode 100644 index 000000000..ae5491bb4 --- /dev/null +++ b/tests/xbps/libxbps/json/Kyuafile @@ -0,0 +1,5 @@ +syntax("kyuafile", 1) + +test_suite("libxbps") + +atf_test_program{name="json_test"} diff --git a/tests/xbps/libxbps/json/Makefile b/tests/xbps/libxbps/json/Makefile new file mode 100644 index 000000000..ea9786818 --- /dev/null +++ b/tests/xbps/libxbps/json/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../.. +-include $(TOPDIR)/config.mk + +TESTSSUBDIR = xbps/libxbps/json +TEST = json_test +EXTRA_FILES = Kyuafile + +include $(TOPDIR)/mk/test.mk diff --git a/tests/xbps/libxbps/json/main.c b/tests/xbps/libxbps/json/main.c new file mode 100644 index 000000000..37b5fce0c --- /dev/null +++ b/tests/xbps/libxbps/json/main.c @@ -0,0 +1,155 @@ +/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck */ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +ATF_TC(xbps_json_print_escape); + +ATF_TC_HEAD(xbps_json_print_escape, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_escape"); +} + +ATF_TC_BODY(xbps_json_print_escape, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + struct xbps_json_printer p = {0}; + + ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz)); + + ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\"\\\b\f\n\r\t")); + ATF_REQUIRE_EQ(0, fflush(p.file)); + ATF_CHECK_STREQ("\\\"\\\\\\b\\f\\n\\r\\t", buf); + + memset(buf, '\0', bufsz); + ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET)); + ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "09azAZ !$#%^()%")); + ATF_REQUIRE_EQ(0, fflush(p.file)); + ATF_CHECK_STREQ("09azAZ !$#%^()%", buf); + + memset(buf, '\0', bufsz); + ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET)); + ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\x01\x1F")); + ATF_REQUIRE_EQ(0, fflush(p.file)); + ATF_CHECK_STREQ("\\u0001\\u001f", buf); +} + +ATF_TC(xbps_json_print_xbps_dictionary); + +ATF_TC_HEAD(xbps_json_print_xbps_dictionary, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_escape"); +} + +ATF_TC_BODY(xbps_json_print_xbps_dictionary, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + struct xbps_json_printer p = {0}; + xbps_dictionary_t dict; + static const char *s = "" + "\n" + "\n" + "\n" + "array-empty\n" + "\n" + "\n" + "array-numbers\n" + "\n" + " 1\n" + " 2\n" + " 3\n" + "\n" + "dict-empty\n" + "\n" + "num-signed\n" + "1\n" + "num-unsigned\n" + "0x1\n" + "string\n" + "hello world\n" + "\n" + "\n"; + + ATF_REQUIRE(dict = xbps_dictionary_internalize(s)); + ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz)); + + ATF_REQUIRE_EQ(0, xbps_json_print_xbps_dictionary(&p, dict)); + ATF_REQUIRE_EQ(0, fflush(p.file)); + ATF_CHECK_STREQ("{\"array-empty\": [], \"array-numbers\": [1, 2, 3], \"dict-empty\": {}, \"num-signed\": 1, \"num-unsigned\": 1, \"string\": \"hello world\"}", buf); +} + +ATF_TC(xbps_json_print_xbps_dictionary_indented); + +ATF_TC_HEAD(xbps_json_print_xbps_dictionary_indented, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_xbps_dictionary: with indents"); +} + +ATF_TC_BODY(xbps_json_print_xbps_dictionary_indented, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + struct xbps_json_printer p = {.indent = 2}; + xbps_dictionary_t dict; + static const char *s = "" + "\n" + "\n" + "\n" + "array-empty\n" + "\n" + "\n" + "array-numbers\n" + "\n" + " 1\n" + " 2\n" + " 3\n" + "\n" + "dict-empty\n" + "\n" + "num-signed\n" + "1\n" + "num-unsigned\n" + "0x1\n" + "string\n" + "hello world\n" + "\n" + "\n"; + + ATF_REQUIRE(dict = xbps_dictionary_internalize(s)); + ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz)); + + ATF_REQUIRE_EQ(0, xbps_json_print_xbps_dictionary(&p, dict)); + ATF_REQUIRE_EQ(0, fflush(p.file)); + ATF_CHECK_STREQ( + "{\n" + " \"array-empty\": [],\n" + " \"array-numbers\": [\n" + " 1,\n" + " 2,\n" + " 3\n" + " ],\n" + " \"dict-empty\": {},\n" + " \"num-signed\": 1,\n" + " \"num-unsigned\": 1,\n" + " \"string\": \"hello world\"\n" + "}", buf); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, xbps_json_print_escape); + ATF_TP_ADD_TC(tp, xbps_json_print_xbps_dictionary); + ATF_TP_ADD_TC(tp, xbps_json_print_xbps_dictionary_indented); + return atf_no_error(); +} From 8efe5580cfb469959241a476665dae033e0f5ccd Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 18 Sep 2023 22:43:20 +0200 Subject: [PATCH 19/34] lib/format.c: add json value conversion --- bin/xbps-query/xbps-query.1 | 7 +++++-- lib/format.c | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 3db83d686..1c89e5d41 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -312,9 +312,9 @@ Format strings are parsed by the following EBNF: | "true" | "false" -- default boolean. | ('"' (("\\" (escape|'"')) | [^"])* '"') -- default string. - ::= humanize | strmode + ::= humanize | strmode | json --- Convert inode status information into a symbolic string +-- Convert inode status information into a symbolic string. ::= "strmode" -- Format a number into a human readable form, the default is:`humanize .8Ki`: @@ -334,6 +334,9 @@ Format strings are parsed by the following EBNF: | "E" -- exa ::= "i" -- Use IEEE/IEC (and now also SI) power of two prefixes. +-- Format value as json value. + ::= "json" + ::= [[fill] align] [sign] [width] ["." precision] [type] ::= -- The character to use when aligning the output. ::= "<" -- Left align. diff --git a/lib/format.c b/lib/format.c index 7900190dc..bdff3f70b 100644 --- a/lib/format.c +++ b/lib/format.c @@ -28,6 +28,7 @@ #include #include +#include "xbps/json.h" #include "xbps_api_impl.h" #include "compat.h" @@ -283,7 +284,7 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, } struct xbps_fmt_conv { - enum { HUMANIZE = 1, STRMODE } type; + enum { HUMANIZE = 1, STRMODE, JSON } type; union { struct humanize { unsigned width : 8; @@ -361,6 +362,10 @@ parse_conversion(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_conv *c fmt->conv->type = HUMANIZE; *pos += sizeof("humanize"); return parse_humanize(pos, &fmt->conv->humanize); + } else if (strncmp(*pos + 1, "json", sizeof("json") - 1) == 0) { + fmt->conv->type = JSON; + *pos += sizeof("json"); + return 0; } return -EINVAL; } @@ -641,6 +646,12 @@ int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp) { const struct xbps_fmt_spec *spec = fmt->spec; + + if (fmt->conv && fmt->conv->type == JSON) { + struct xbps_json_printer pr = {.file = fp}; + return xbps_json_print_quote(&pr, str); + } + if (len == 0) len = strlen(str); if (spec && spec->align == '>' && spec->width > (unsigned)len) { @@ -700,6 +711,7 @@ xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) switch (fmt->conv->type) { case HUMANIZE: return humanize(&fmt->conv->humanize, fmt, d, fp); case STRMODE: return tostrmode(fmt, d, fp); + case JSON: break; } } if (spec) { @@ -732,6 +744,10 @@ xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) { + if (fmt->conv && fmt->conv->type == JSON) { + struct xbps_json_printer pr = {.file = fp}; + return xbps_json_print_xbps_object(&pr, obj); + } switch (xbps_object_type(obj)) { case XBPS_TYPE_BOOL: return xbps_fmt_print_string(fmt, xbps_bool_true(obj) ? "true" : "false", 0, fp); From 9a95a39755ab7deceb40164189970c3a84eb4bce Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 18 Sep 2023 22:43:59 +0200 Subject: [PATCH 20/34] bin/xbps-query: add --json flag as alternative to --format --- bin/xbps-query/defs.h | 2 +- bin/xbps-query/list.c | 35 ++++++++++++++++++++++++----------- bin/xbps-query/main.c | 23 ++++++++++++++++------- bin/xbps-query/xbps-query.1 | 2 ++ 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 0f4623114..1ce503333 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -55,7 +55,7 @@ int ownedby(struct xbps_handle *, const char *, bool, bool); int list_manual_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); int list_orphans(struct xbps_handle *, const char *); int list_pkgs_pkgdb(struct xbps_handle *); -int list_pkgdb(struct xbps_handle *, int (*filter)(xbps_object_t), const char *format); +int list_pkgdb(struct xbps_handle *, int (*filter)(xbps_object_t), const char *format, int json); int repo_list(struct xbps_handle *); diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index ff46dda28..1c853c3f8 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -36,6 +36,7 @@ #include "defs.h" #include "xbps.h" +#include "xbps/json.h" struct length_max_cb { const char *key; @@ -135,6 +136,7 @@ list_pkgs_pkgdb(struct xbps_handle *xhp) struct list_pkgdb_cb { struct xbps_fmt *fmt; + struct xbps_json_printer *json; int (*filter)(xbps_object_t obj); }; @@ -153,23 +155,34 @@ list_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, return 0; } - r = xbps_fmt_dictionary(ctx->fmt, obj, stdout); - if (r < 0) - return r; - return 0; + if (ctx->fmt) { + r = xbps_fmt_dictionary(ctx->fmt, obj, stdout); + } else if (ctx->json) { + r = xbps_json_print_xbps_object(ctx->json, obj); + fprintf(ctx->json->file, "\n"); + } else { + r = -ENOTSUP; + } + return r; } int -list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *format) +list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *format, int json) { struct list_pkgdb_cb ctx = {.filter = filter}; + struct xbps_json_printer pr = {0}; int r; - - ctx.fmt = xbps_fmt_parse(format); - if (!ctx.fmt) { - r = -errno; - xbps_error_printf("failed to parse format: %s\n", strerror(-r)); - return r; + if (json > 0) { + pr.indent = (json-1) * 2; + pr.file = stdout; + ctx.json = ≺ + } else if (format) { + ctx.fmt = xbps_fmt_parse(format); + if (!ctx.fmt) { + r = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-r)); + return r; + } } r = xbps_pkgdb_foreach_cb(xhp, list_pkgdb_cb, &ctx); xbps_fmt_free(ctx.fmt); diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 42afddb99..0839cb26c 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -33,6 +33,8 @@ #include #include "defs.h" +#include "xbps.h" +#include "xbps/json.h" static void __attribute__((noreturn)) usage(bool fail) @@ -46,6 +48,7 @@ usage(bool fail) " -F, --format Format for list output\n" " -h, --help Show usage\n" " -i, --ignore-conf-repos Ignore repositories defined in xbps.d\n" + " -J, --json Print output as json\n" " -M, --memory-sync Remote repository data is fetched and stored\n" " in memory, ignoring on-disk repodata archives\n" " -p, --property PROP[,...] Show properties for PKGNAME\n" @@ -100,13 +103,14 @@ filter_repolock(xbps_object_t obj) int main(int argc, char **argv) { - const char *shortopts = "C:c:dF:f:hHiLlMmOo:p:Rr:s:S:VvX:x:"; + const char *shortopts = "C:c:dF:f:hHiJLlMmOo:p:Rr:s:S:VvX:x:"; const struct option longopts[] = { { "config", required_argument, NULL, 'C' }, { "cachedir", required_argument, NULL, 'c' }, { "debug", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "ignore-conf-repos", no_argument, NULL, 'i' }, + { "json", no_argument, NULL, 'J' }, { "list-repos", no_argument, NULL, 'L' }, { "list-pkgs", no_argument, NULL, 'l' }, { "list-hold-pkgs", no_argument, NULL, 'H' }, @@ -137,6 +141,7 @@ main(int argc, char **argv) bool list_pkgs, list_repos, orphans, own, list_repolock; bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; + int json = 0; rootdir = cachedir = confdir = props = pkg = catfile = format = NULL; flags = rv = c = 0; @@ -165,6 +170,9 @@ main(int argc, char **argv) case 'F': format = optarg; break; + case 'J': + json++; + break; case 'H': list_hold = opmode = true; break; @@ -286,20 +294,21 @@ main(int argc, char **argv) rv = repo_list(&xh); } else if (list_hold) { - rv = list_pkgdb(&xh, filter_hold, format ? format : "{pkgver}\n") < 0; + rv = list_pkgdb(&xh, filter_hold, format ? format : "{pkgver}\n", json) < 0; } else if (list_repolock) { - rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n") < 0; + rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n", json) < 0; } else if (list_manual) { - rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n") < 0; + rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n", json) < 0; } else if (list_pkgs) { /* list available pkgs */ - if (format) - rv = list_pkgdb(&xh, NULL, format); - else + if (format || json > 0) { + rv = list_pkgdb(&xh, NULL, format, json); + } else { rv = list_pkgs_pkgdb(&xh); + } } else if (orphans) { /* list pkg orphans */ diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 1c89e5d41..8e836faa8 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -100,6 +100,8 @@ Ignore repositories defined in configuration files. Only repositories specified in the command line via .Ar --repository will be used. +.It Fl J, Fl -json +Print output as json. .It Fl M, Fl -memory-sync For remote repositories, the data is fetched and stored in memory for the current operation. From fb28e5ca57ed96f5692d853770f333d268cbf094 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Tue, 14 Mar 2023 22:12:14 -0400 Subject: [PATCH 21/34] lib/format: add \e escape, fix typos in format spec, remove dbg print --- bin/xbps-query/xbps-query.1 | 10 +++++----- lib/format.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 8e836faa8..b8937c518 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -305,13 +305,13 @@ Format strings are parsed by the following EBNF: .Bd -literal ::= (prefix | "\\" (escape|[{}]) | substitution)* ::= [^\\{}]+ -- Literal text chunk. - ::= [abfnrtv0] -- POSIX-like espace character. + ::= [abefnrtv0] -- POSIX-like escape character. ::= "{" variable ["?" default] ["!" conversion] [":" format] "}" ::= [a-zA-Z0-9_-] - ::= ([-]?[0-9]+) -- default number. - | "true" | "false" -- default boolean. + ::= ([-]?[0-9]+) -- default number. + | "true" | "false" -- default boolean. | ('"' (("\\" (escape|'"')) | [^"])* '"') -- default string. ::= humanize | strmode | json @@ -326,7 +326,7 @@ Format strings are parsed by the following EBNF: it using one digit. ::= [0-9]+ -- Width of the output. ::= multiplier -- Minimum scale multiplier and optionally - [multiplier] -- Maxium scale multiplier. + [multiplier] -- Maximum scale multiplier. ::= "B" -- byte | "K" -- kilo | "M" -- mega @@ -347,7 +347,7 @@ Format strings are parsed by the following EBNF: ::= "+" -- Add sign to positive and negative numbers. | "-" -- Add sign to negative numbers. ::= [0-9]+ -- The alignment width. - ::= [0-9]+ -- Percision for numbers. + ::= [0-9]+ -- Precision for numbers. ::= "d" -- Decimal number. | "o" -- Octal number. | "u" -- Unsigned number. diff --git a/lib/format.c b/lib/format.c index bdff3f70b..d53edf24d 100644 --- a/lib/format.c +++ b/lib/format.c @@ -131,6 +131,7 @@ nexttok(enum tok *tok, const char **pos, struct strbuf *buf) case '\\': r = strbuf_putc(buf, '\\'); break; case 'a': r = strbuf_putc(buf, '\a'); break; case 'b': r = strbuf_putc(buf, '\b'); break; + case 'e': r = strbuf_putc(buf, '\e'); break; case 'f': r = strbuf_putc(buf, '\f'); break; case 'n': r = strbuf_putc(buf, '\n'); break; case 'r': r = strbuf_putc(buf, '\r'); break; @@ -553,7 +554,6 @@ xbps_fmt_parse(const char *format) if (r < 0) goto err; } - fprintf(stderr, "fmt: prefix='%s' var='%s'\n", fmt[n].prefix, fmt[n].var); n++; } out: From 5640d5707357aa65b96d85853a89f7bd686e48ba Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 18 Sep 2023 23:40:12 +0200 Subject: [PATCH 22/34] bin/xbps-query: make --json compact if there is no indention --- bin/xbps-query/list.c | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index 1c853c3f8..913d79330 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -174,6 +174,7 @@ list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *fo int r; if (json > 0) { pr.indent = (json-1) * 2; + pr.compact = pr.indent == 0; pr.file = stdout; ctx.json = ≺ } else if (format) { From c0422e2a6b997fdd73916e1860fe45205c82f4f4 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 16:07:37 +0200 Subject: [PATCH 23/34] bin/xbps-query: cleanup mode handling --- bin/xbps-query/main.c | 188 ++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 98 deletions(-) diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 0839cb26c..bf234d6c2 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -135,37 +135,45 @@ main(int argc, char **argv) { "cat", required_argument, NULL, 2 }, { NULL, 0, NULL, 0 }, }; - struct xbps_handle xh; - const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile, *format; - int c, flags, rv; - bool list_pkgs, list_repos, orphans, own, list_repolock; - bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; - bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; + struct xbps_handle xh = {0}; + const char *pkg = NULL, *props = NULL, *catfile, *format; + int c, rv; + bool regex = false, repo_mode = false, fulldeptree = false; int json = 0; + enum { + CAT_FILE = 1, + LIST_HOLD, + LIST_INSTALLED, + LIST_MANUAL, + LIST_ORPHANS, + LIST_REPOLOCK, + SHOW_REPOS, + SEARCH_FILE, + SEARCH_PKG, + SHOW_DEPS, + SHOW_FILES, + SHOW_PKG, + SHOW_REVDEPS, + } mode = 0; - rootdir = cachedir = confdir = props = pkg = catfile = format = NULL; - flags = rv = c = 0; - list_pkgs = list_repos = list_hold = orphans = pkg_search = own = false; - list_manual = list_repolock = show_prop = show_files = false; - regex = show = show_deps = show_rdeps = fulldeptree = false; - repo_mode = opmode = false; - - memset(&xh, 0, sizeof(xh)); + props = pkg = catfile = format = NULL; + rv = c = 0; + repo_mode = false; while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { switch (c) { case 'C': - confdir = optarg; + xbps_strlcpy(xh.confdir, optarg, sizeof(xh.confdir)); break; case 'c': - cachedir = optarg; + xbps_strlcpy(xh.cachedir, optarg, sizeof(xh.cachedir)); break; case 'd': - flags |= XBPS_FLAG_DEBUG; + xh.flags |= XBPS_FLAG_DEBUG; break; case 'f': pkg = optarg; - show_files = opmode = true; + mode = SHOW_FILES; break; case 'F': format = optarg; @@ -174,36 +182,35 @@ main(int argc, char **argv) json++; break; case 'H': - list_hold = opmode = true; + mode = LIST_HOLD; break; case 'h': usage(false); /* NOTREACHED */ case 'i': - flags |= XBPS_FLAG_IGNORE_CONF_REPOS; + xh.flags |= XBPS_FLAG_IGNORE_CONF_REPOS; break; case 'L': - list_repos = opmode = true; + mode = SHOW_REPOS; break; case 'l': - list_pkgs = opmode = true; + mode = LIST_INSTALLED; break; case 'M': - flags |= XBPS_FLAG_REPOS_MEMSYNC; + xh.flags |= XBPS_FLAG_REPOS_MEMSYNC; break; case 'm': - list_manual = opmode = true; + mode = LIST_MANUAL; break; case 'O': - orphans = opmode = true; + mode = LIST_ORPHANS; break; case 'o': pkg = optarg; - own = opmode = true; + mode = SEARCH_FILE; break; case 'p': props = optarg; - show_prop = true; break; case 'R': if (optarg != NULL) { @@ -212,29 +219,29 @@ main(int argc, char **argv) repo_mode = true; break; case 'r': - rootdir = optarg; + xbps_strlcpy(xh.rootdir, optarg, sizeof(xh.rootdir)); break; case 'S': pkg = optarg; - show = opmode = true; + mode = SHOW_PKG; break; case 's': pkg = optarg; - pkg_search = opmode = true; + mode = SEARCH_PKG; break; case 'v': - flags |= XBPS_FLAG_VERBOSE; + xh.flags |= XBPS_FLAG_VERBOSE; break; case 'V': printf("%s\n", XBPS_RELVER); exit(EXIT_SUCCESS); case 'x': pkg = optarg; - show_deps = opmode = true; + mode = SHOW_DEPS; break; case 'X': pkg = optarg; - show_rdeps = opmode = true; + mode = SHOW_REVDEPS; break; case 0: regex = true; @@ -243,10 +250,11 @@ main(int argc, char **argv) fulldeptree = true; break; case 2: + mode = CAT_FILE; catfile = optarg; break; case 3: - list_repolock = opmode = true; + mode = LIST_REPOLOCK; break; case '?': default: @@ -257,99 +265,83 @@ main(int argc, char **argv) argc -= optind; argv += optind; - if (!argc && !opmode) { - usage(true); - /* NOTREACHED */ - } else if (!opmode) { - /* show mode by default */ - show = opmode = true; + /* no mode (defaults to show) and cat mode take a trailing argv */ + if (mode == 0 || mode == CAT_FILE) { + if (argc == 0) + usage(true); + if (mode == 0) + mode = SHOW_PKG; pkg = *(argv++); argc--; } - if (argc) { - /* trailing parameters */ + + /* trailing parameters */ + if (argc != 0) usage(true); - /* NOTREACHED */ - } + /* * Initialize libxbps. */ - if (rootdir) - xbps_strlcpy(xh.rootdir, rootdir, sizeof(xh.rootdir)); - if (cachedir) - xbps_strlcpy(xh.cachedir, cachedir, sizeof(xh.cachedir)); - if (confdir) - xbps_strlcpy(xh.confdir, confdir, sizeof(xh.confdir)); - - xh.flags = flags; - if ((rv = xbps_init(&xh)) != 0) { xbps_error_printf("Failed to initialize libxbps: %s\n", strerror(rv)); exit(EXIT_FAILURE); } - if (list_repos) { - /* list repositories */ - rv = repo_list(&xh); - - } else if (list_hold) { + switch (mode) { + case LIST_HOLD: rv = list_pkgdb(&xh, filter_hold, format ? format : "{pkgver}\n", json) < 0; - - } else if (list_repolock) { - rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n", json) < 0; - - } else if (list_manual) { - rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n", json) < 0; - - } else if (list_pkgs) { - /* list available pkgs */ + break; + case LIST_INSTALLED: if (format || json > 0) { rv = list_pkgdb(&xh, NULL, format, json); } else { rv = list_pkgs_pkgdb(&xh); } - - } else if (orphans) { - /* list pkg orphans */ + break; + case LIST_MANUAL: + rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n", json) < 0; + break; + case LIST_ORPHANS: rv = list_orphans(&xh, format ? format : "{pkgver}\n") < 0; - - } else if (own) { - /* ownedby mode */ + break; + case LIST_REPOLOCK: + rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n", json) < 0; + break; + case SHOW_REPOS: + rv = repo_list(&xh); + break; + case SEARCH_FILE: rv = ownedby(&xh, pkg, repo_mode, regex); - - } else if (pkg_search) { - /* search mode */ + break; + case SEARCH_PKG: rv = search(&xh, repo_mode, pkg, props, regex); - - } else if (catfile) { - /* repo cat file mode */ + break; + case SHOW_DEPS: + rv = show_pkg_deps(&xh, pkg, repo_mode, fulldeptree); + break; + case SHOW_FILES: if (repo_mode) - rv = repo_cat_file(&xh, pkg, catfile); + rv = repo_show_pkg_files(&xh, pkg); else - rv = cat_file(&xh, pkg, catfile); - } else if (show || show_prop) { - /* show mode */ + rv = show_pkg_files_from_metadir(&xh, pkg); + break; + case CAT_FILE: if (repo_mode) - rv = repo_show_pkg_info(&xh, pkg, props); + rv = repo_cat_file(&xh, pkg, catfile); else - rv = show_pkg_info_from_metadir(&xh, pkg, props); - - } else if (show_files) { - /* show-files mode */ + rv = cat_file(&xh, pkg, catfile); + break; + case SHOW_PKG: if (repo_mode) - rv = repo_show_pkg_files(&xh, pkg); + rv = repo_show_pkg_info(&xh, pkg, props); else - rv = show_pkg_files_from_metadir(&xh, pkg); - - } else if (show_deps) { - /* show-deps mode */ - rv = show_pkg_deps(&xh, pkg, repo_mode, fulldeptree); - - } else if (show_rdeps) { - /* show-rdeps mode */ + rv = show_pkg_info_from_metadir(&xh, pkg, props); + break; + case SHOW_REVDEPS: rv = show_pkg_revdeps(&xh, pkg, repo_mode); - } + break; + } xbps_end(&xh); exit(rv); From 6a5dae8cefd1d5e5e2fe3fd34be59092edbeaf5d Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 16:15:23 +0200 Subject: [PATCH 24/34] bin/xbps-query: move --list-repo to main.c, list.c is exclusively package lists now --- bin/xbps-query/defs.h | 2 -- bin/xbps-query/list.c | 56 ------------------------------------------- bin/xbps-query/main.c | 47 +++++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 1ce503333..72fa5500c 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -57,8 +57,6 @@ int list_orphans(struct xbps_handle *, const char *); int list_pkgs_pkgdb(struct xbps_handle *); int list_pkgdb(struct xbps_handle *, int (*filter)(xbps_object_t), const char *format, int json); -int repo_list(struct xbps_handle *); - /* from search.c */ int search(struct xbps_handle *, bool, const char *, const char *, bool); diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index 913d79330..a70e26ad6 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -242,59 +242,3 @@ list_orphans(struct xbps_handle *xhp, const char *format) xbps_fmt_free(fmt); return r; } - -#ifndef __UNCONST -#define __UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) -#endif - -static void -repo_list_uri(struct xbps_repo *repo) -{ - const char *signedby = NULL; - uint16_t pubkeysize = 0; - - printf("%5zd %s", - repo->idx ? (ssize_t)xbps_dictionary_count(repo->idx) : -1, - repo->uri); - printf(" (RSA %s)\n", repo->is_signed ? "signed" : "unsigned"); - if (repo->xhp->flags & XBPS_FLAG_VERBOSE) { - xbps_data_t pubkey; - char *hexfp = NULL; - - xbps_dictionary_get_cstring_nocopy(repo->idxmeta, "signature-by", &signedby); - xbps_dictionary_get_uint16(repo->idxmeta, "public-key-size", &pubkeysize); - pubkey = xbps_dictionary_get(repo->idxmeta, "public-key"); - if (pubkey) - hexfp = xbps_pubkey2fp(pubkey); - if (signedby) - printf(" Signed-by: %s\n", signedby); - if (pubkeysize && hexfp) - printf(" %u %s\n", pubkeysize, hexfp); - if (hexfp) - free(hexfp); - } -} - -static void -repo_list_uri_err(const char *repouri) -{ - printf("%5zd %s (RSA maybe-signed)\n", (ssize_t) -1, repouri); -} - -int -repo_list(struct xbps_handle *xhp) -{ - for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { - const char *repouri = NULL; - struct xbps_repo *repo; - xbps_array_get_cstring_nocopy(xhp->repositories, i, &repouri); - repo = xbps_repo_open(xhp, repouri); - if (!repo) { - repo_list_uri_err(repouri); - continue; - } - repo_list_uri(repo); - xbps_repo_release(repo); - } - return 0; -} diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index bf234d6c2..e0c8f47e2 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -80,6 +80,51 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } +static void +show_repo(struct xbps_repo *repo) +{ + xbps_data_t pubkey; + const char *signedby = NULL; + char *hexfp = NULL; + uint16_t pubkeysize = 0; + + printf("%5zd %s", + repo->idx ? (ssize_t)xbps_dictionary_count(repo->idx) : -1, + repo->uri); + printf(" (RSA %s)\n", repo->is_signed ? "signed" : "unsigned"); + if (!(repo->xhp->flags & XBPS_FLAG_VERBOSE)) + return; + + xbps_dictionary_get_cstring_nocopy(repo->idxmeta, "signature-by", &signedby); + xbps_dictionary_get_uint16(repo->idxmeta, "public-key-size", &pubkeysize); + pubkey = xbps_dictionary_get(repo->idxmeta, "public-key"); + if (pubkey) + hexfp = xbps_pubkey2fp(pubkey); + if (signedby) + printf(" Signed-by: %s\n", signedby); + if (pubkeysize && hexfp) + printf(" %u %s\n", pubkeysize, hexfp); + free(hexfp); +} + +static int +show_repos(struct xbps_handle *xhp) +{ + for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { + const char *repouri = NULL; + struct xbps_repo *repo; + xbps_array_get_cstring_nocopy(xhp->repositories, i, &repouri); + repo = xbps_repo_open(xhp, repouri); + if (!repo) { + printf("%5zd %s (RSA maybe-signed)\n", (ssize_t)-1, repouri); + continue; + } + show_repo(repo); + xbps_repo_release(repo); + } + return 0; +} + static int filter_hold(xbps_object_t obj) { @@ -309,7 +354,7 @@ main(int argc, char **argv) rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n", json) < 0; break; case SHOW_REPOS: - rv = repo_list(&xh); + rv = show_repos(&xh); break; case SEARCH_FILE: rv = ownedby(&xh, pkg, repo_mode, regex); From 66c62166fdf6a4dfe083a15c7ba4fa0a754bc519 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 17:00:27 +0200 Subject: [PATCH 25/34] bin/xbps-query: add format string support to --list-repos --- bin/xbps-query/main.c | 37 ++++++++++++++++++++++++++++++++++--- bin/xbps-query/xbps-query.1 | 15 +++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index e0c8f47e2..210e1fad3 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -80,6 +80,20 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } +static int +repo_fmt(FILE *fp, const struct xbps_fmt *fmt, void *data) +{ + struct xbps_repo *repo = data; + if (strcmp(fmt->var, "url") == 0) { + return xbps_fmt_print_string(fmt, repo->uri, 0, fp); + } else if (strcmp(fmt->var, "signed") == 0) { + return xbps_fmt_print_string(fmt, repo->is_signed ? "true" : "false", 0, fp); + } else if (strcmp(fmt->var, "packages") == 0) { + return xbps_fmt_print_number(fmt, xbps_dictionary_count(repo->idx), fp); + } + return 0; +} + static void show_repo(struct xbps_repo *repo) { @@ -108,8 +122,16 @@ show_repo(struct xbps_repo *repo) } static int -show_repos(struct xbps_handle *xhp) +show_repos(struct xbps_handle *xhp, const char *format) { + struct xbps_fmt *fmt = NULL; + if (format) { + fmt = xbps_fmt_parse(format); + if (!fmt) { + xbps_error_printf("failed to parse format: %s\n", strerror(errno)); + return 1; + } + } for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { const char *repouri = NULL; struct xbps_repo *repo; @@ -119,9 +141,18 @@ show_repos(struct xbps_handle *xhp) printf("%5zd %s (RSA maybe-signed)\n", (ssize_t)-1, repouri); continue; } - show_repo(repo); + if (fmt) { + int r = xbps_fmt(fmt, repo_fmt, repo, stdout); + if (r < 0) { + xbps_error_printf("failed to format repo: %s\n", strerror(-r)); + return 1; + } + } else { + show_repo(repo); + } xbps_repo_release(repo); } + xbps_fmt_free(fmt); return 0; } @@ -354,7 +385,7 @@ main(int argc, char **argv) rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n", json) < 0; break; case SHOW_REPOS: - rv = show_repos(&xh); + rv = show_repos(&xh, format); break; case SEARCH_FILE: rv = ownedby(&xh, pkg, repo_mode, regex); diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index b8937c518..1132cfdac 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -181,6 +181,21 @@ If a repository is not available the number of packages will be The .Fl v option can be used to show more detailed information of remote repositories. +.Pp +The following +.Fl F , Fl -format +variables can be used: +.Bl -tag -compact -width Ic +.It Ic url +repository url. +.It Ic signed +.Sq true +if the repository is signed or +.Sq false +otherwise. +.It Ic packages +The number of packages in the repository. +.El .It Fl H, Fl -list-hold-pkgs List registered packages in the package database (pkgdb) that are on .Sy hold . From 22cd69378011ad3a5aca98040cf776dbd63708ab Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 17:58:42 +0200 Subject: [PATCH 26/34] bin/xbps-query: document the --json flag better --- bin/xbps-query/xbps-query.1 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 1132cfdac..e4f9533da 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -101,7 +101,22 @@ Only repositories specified in the command line via .Ar --repository will be used. .It Fl J, Fl -json -Print output as json. +Format the output as newline-delimited JSON objects. +If the flag is specified more than once the output will be +.Dq pretty-printed +adding additional line breaks and indentions to the output. +For each repetition two levels of indention are added. +.Pp +The output for the following modes will be formatted as followed: +.Bl -tag -width -x -compact +.It Fl l , Fl -list-pkgs +.It Fl H , Fl -list-hold-pkgs +.It Fl -list-repolock-pkgs +.It Fl m , Fl -list-manual-pkgs +.It Fl O , Fl -list-orphans +JSON objects containing the package +.Sx PROPERTIES . +.El .It Fl M, Fl -memory-sync For remote repositories, the data is fetched and stored in memory for the current operation. From 95e85687dd6e16da9861e5c2457a2c6a280b97bf Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 20:57:59 +0200 Subject: [PATCH 27/34] lib/format.c: IWYU --- lib/format.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/format.c b/lib/format.c index d53edf24d..88640504a 100644 --- a/lib/format.c +++ b/lib/format.c @@ -28,9 +28,16 @@ #include #include +#include "compat.h" #include "xbps/json.h" +#include "xbps/xbps_array.h" +#include "xbps/xbps_bool.h" +#include "xbps/xbps_data.h" +#include "xbps/xbps_dictionary.h" +#include "xbps/xbps_number.h" +#include "xbps/xbps_object.h" +#include "xbps/xbps_string.h" #include "xbps_api_impl.h" -#include "compat.h" /** * @file lib/format.c @@ -638,10 +645,6 @@ xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) return 0; } -struct fmt_dict_cb { - xbps_dictionary_t dict; -}; - int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp) { @@ -775,10 +778,14 @@ xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) return 0; } +struct fmt_dict_ctx { + xbps_dictionary_t dict; +}; + static int fmt_dict_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) { - struct fmt_dict_cb *ctx = data; + struct fmt_dict_ctx *ctx = data; xbps_object_t obj = xbps_dictionary_get(ctx->dict, fmt->var); return xbps_fmt_print_object(fmt, obj, fp); } @@ -786,13 +793,13 @@ fmt_dict_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp) { - struct fmt_dict_cb ctx = {.dict = dict}; + struct fmt_dict_ctx ctx = {.dict = dict}; return xbps_fmt(fmt, &fmt_dict_cb, &ctx, fp); } int xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp) { - struct fmt_dict_cb ctx = {.dict = dict}; + struct fmt_dict_ctx ctx = {.dict = dict}; return xbps_fmts(format, &fmt_dict_cb, &ctx, fp); } From b5e66ba474bfbca2e05f1e66a61616b8f64d4a34 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 19 Sep 2023 21:05:12 +0200 Subject: [PATCH 28/34] include: split xbps_fmt stuff into its own header --- include/xbps.h.in | 244 ------------------------------------------ include/xbps/fmt.h | 257 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 244 deletions(-) create mode 100644 include/xbps/fmt.h diff --git a/include/xbps.h.in b/include/xbps.h.in index 2566b3443..5ccd51e94 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2370,250 +2370,6 @@ xbps_plist_dictionary_from_file(const char *path); /**@}*/ -/** @addtogroup format */ -/**@{*/ - -/** - * @struct xbps_fmt xbps.h "xbps.h" - * @brief Structure of parsed format string variable. - */ -struct xbps_fmt { - /** - * @private - * @var prefix - * @brief Prefix of the format chunk. - */ - char *prefix; - /** - * @var var - * @brief Variable name. - */ - char *var; - /** - * @var def - * @brief Default value. - */ - struct xbps_fmt_def *def; - /** - * @var conv - * @brief Format conversion. - */ - struct xbps_fmt_conv *conv; - /** - * @var spec - * @brief Format specification. - */ - struct xbps_fmt_spec *spec; -}; - -/** - * @struct xbps_fmt_def xbps.h "xbps.h" - * @brief Structure of parsed format specifier. - */ -struct xbps_fmt_def { - enum { - XBPS_FMT_DEF_STR = 1, - XBPS_FMT_DEF_NUM, - XBPS_FMT_DEF_BOOL, - } type; - union { - char *str; - int64_t num; - bool boolean; - } val; -}; - -/** - * @struct xbps_fmt_spec xbps.h "xbps.h" - * @brief Structure of parsed format specifier. - */ -struct xbps_fmt_spec { - /** - * @var fill - * @brief Padding character. - */ - char fill; - /** - * @var align - * @brief Alignment modifier. - * - * Possible values are: - * - `<`: left align. - * - `>`: right align. - * - `=`: place padding after the sign. - */ - char align; - /** - * @var sign - * @brief Sign modifier. - * - * Possible values are: - * - `-`: sign negative numbers. - * - `+`: sign both negative and positive numbers. - * - space: sign negative numbers and add space before positive numbers. - */ - char sign; - /** - * @var width - * @brief Minimum width. - */ - unsigned int width; - /** - * @var precision - * @brief Precision. - */ - unsigned int precision; - /** - * @var type - * @brief Type specifier usually to change the output format type. - * - * Can contain any character, xbps_fmt_number() uses the following: - * - `u`: Unsigned decimal. - * - `d`: Decimal. - * - `x`: Hex with lowercase letters. - * - `X`: hex with uppercase letters. - * - `h`: Human readable using humanize_number(3). - */ - char type; -}; - -/** - * @brief Format callback, called for each variable in the format string. - * - * A callback function should write data associated with \a var to \a fp and use - * \a w as alignment specifier. - * - * @param[in] fp The file to print to. - * @param[in] spec The format specifier. - * @param[in] var The format string variable name. - * @param[in] data Userdata passed to the xbps_fmt() function. - */ -typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt *fmt, void *data); - -/** - * @brief Parses the format string \a format. - * - * @param[in] format The format string. - * - * @return The parsed format structure, or NULL on error. - * The returned buffer must be freed with xbps_fmt_free(). - * @retval EINVAL Invalid format string. - * @retval ERANGE Invalid alignment specifier. - * @retval ENOMEM Memory allocation failure. - */ -struct xbps_fmt *xbps_fmt_parse(const char *format); - -/** - * @brief Releases memory associated with \a fmt. - * - * @param[in] fmt The format string. - */ -void xbps_fmt_free(struct xbps_fmt *fmt); - -/** - * @brief Print formatted text to \a fp. - * - * @param[in] fmt Format returned by struct xbps_fmt_parse(). - * @param[in] cb Callback function called for each variable in the format. - * @param[in] data Userdata passed to the callback \a cb. - * @param[in] fp File to print to. - * - * @return 0 on success or a negative errno. - * @retval 0 Success - */ -int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp); - -/** - * @brief Print formatted dictionary values to \a fp. - * - * Prints formatted dictionary values as specified by the parsed \a fmt - * format string to \a fp. - * - * @param[in] fmt Format returned by struct xbps_fmt_parse(). - * @param[in] dict Dictionary to print values from. - * @param[in] fp File to print to. - * - * @return 0 on success or value returned by \a cb. - * @retval 0 Success - */ -int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp); - -/** - * @brief Print formatted dictionary values to \a fp. - * - * Prints formatted dictionary values as specified by the format string - * \a format to \a fp. - * - * @param[in] format Format string. - * @param[in] dict Dictionary to print values from. - * @param[in] fp File to print to. - * - * @return 0 on success or value returned by \a cb. - * @retval 0 Success - */ -int xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp); - -/** - * @brief Print formatted dictionary to \a fp. - * - * Print the formatted dictionary according to the \a format format string - * to \a fp. - * - * @param[in] format Format string. - * @param[in] cb Callback function called for each variable in the format. - * @param[in] data Userdata passed to the callback \a cb. - * @param[in] fp File to print to. - * - * @return 0 on success. - * @retval 0 Success. - * @retval -EINVAL Invalid format string. - * @retval -ERANGE Invalid alignment specifier. - * @retval -ENOMEM Memory allocation failure. - */ -int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); - -/** - * @brief Print formatted number to \a fp. - * - * Prints the number \a num to \a fp according to the specification \a spec. - * - * @param[in] spec Format specification. - * @param[in] num Number to print. - * @param[in] fp File to print to. - * - * @return Returns 0 on success. - */ -int xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t num, FILE *fp); - -/** - * @brief Print formatted string to \a fp. - * - * Prints the string \a str to \a fp according to the specification \a spec. - * - * @param[in] spec Format specification. - * @param[in] str String to print. - * @param[in] len Length of the string or 0. - * @param[in] fp File to print to. - * - * @return Returns 0 on success. - */ -int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp); - -/** - * @brief Print formatted ::xbps_object_t to \a fp. - * - * Prints the ::xbps_object_t \a obj to \a fp according to the specification \a spec. - * - * @param[in] spec Format specification. - * @param[in] obj The object to print. - * @param[in] fp File to print to. - * - * @return Returns 0 on success. - */ -int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp); - -/**@}*/ - #ifdef __cplusplus } #endif diff --git a/include/xbps/fmt.h b/include/xbps/fmt.h new file mode 100644 index 000000000..55f842981 --- /dev/null +++ b/include/xbps/fmt.h @@ -0,0 +1,257 @@ +/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck */ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#ifndef _XBPS_FMT_H_ +#define _XBPS_FMT_H_ + +#include +#include +#include + +#include +#include + +/** @addtogroup format */ +/**@{*/ + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format string variable. + */ +struct xbps_fmt { + /** + * @private + * @var prefix + * @brief Prefix of the format chunk. + */ + char *prefix; + /** + * @var var + * @brief Variable name. + */ + char *var; + /** + * @var def + * @brief Default value. + */ + struct xbps_fmt_def *def; + /** + * @var conv + * @brief Format conversion. + */ + struct xbps_fmt_conv *conv; + /** + * @var spec + * @brief Format specification. + */ + struct xbps_fmt_spec *spec; +}; + +/** + * @struct xbps_fmt_def xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_def { + enum { + XBPS_FMT_DEF_STR = 1, + XBPS_FMT_DEF_NUM, + XBPS_FMT_DEF_BOOL, + } type; + union { + char *str; + int64_t num; + bool boolean; + } val; +}; + +/** + * @struct xbps_fmt_spec xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_spec { + /** + * @var fill + * @brief Padding character. + */ + char fill; + /** + * @var align + * @brief Alignment modifier. + * + * Possible values are: + * - `<`: left align. + * - `>`: right align. + * - `=`: place padding after the sign. + */ + char align; + /** + * @var sign + * @brief Sign modifier. + * + * Possible values are: + * - `-`: sign negative numbers. + * - `+`: sign both negative and positive numbers. + * - space: sign negative numbers and add space before positive numbers. + */ + char sign; + /** + * @var width + * @brief Minimum width. + */ + unsigned int width; + /** + * @var precision + * @brief Precision. + */ + unsigned int precision; + /** + * @var type + * @brief Type specifier usually to change the output format type. + * + * Can contain any character, xbps_fmt_number() uses the following: + * - `u`: Unsigned decimal. + * - `d`: Decimal. + * - `x`: Hex with lowercase letters. + * - `X`: hex with uppercase letters. + * - `h`: Human readable using humanize_number(3). + */ + char type; +}; + +/** + * @brief Format callback, called for each variable in the format string. + * + * A callback function should write data associated with \a var to \a fp and use + * \a w as alignment specifier. + * + * @param[in] fp The file to print to. + * @param[in] fmt The format specifier. + * @param[in] data Userdata passed to the xbps_fmt() function. + */ +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt *fmt, void *data); + +/** + * @brief Parses the format string \a format. + * + * @param[in] format The format string. + * + * @return The parsed format structure, or NULL on error. + * The returned buffer must be freed with xbps_fmt_free(). + * @retval EINVAL Invalid format string. + * @retval ERANGE Invalid alignment specifier. + * @retval ENOMEM Memory allocation failure. + */ +struct xbps_fmt *xbps_fmt_parse(const char *format); + +/** + * @brief Releases memory associated with \a fmt. + * + * @param[in] fmt The format string. + */ +void xbps_fmt_free(struct xbps_fmt *fmt); + +/** + * @brief Print formatted text to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success or a negative errno. + * @retval 0 Success + */ +int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the parsed \a fmt + * format string to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the format string + * \a format to \a fp. + * + * @param[in] format Format string. + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary to \a fp. + * + * Print the formatted dictionary according to the \a format format string + * to \a fp. + * + * @param[in] format Format string. + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success. + * @retval 0 Success. + * @retval -EINVAL Invalid format string. + * @retval -ERANGE Invalid alignment specifier. + * @retval -ENOMEM Memory allocation failure. + */ +int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted number to \a fp. + * + * Prints the number \a num to \a fp according to the specification \a spec. + * + * @param[in] fmt Format specification. + * @param[in] num Number to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t num, FILE *fp); + +/** + * @brief Print formatted string to \a fp. + * + * Prints the string \a str to \a fp according to the specification \a spec. + * + * @param[in] fmt Format specification. + * @param[in] str String to print. + * @param[in] len Length of the string or 0. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp); + +/** + * @brief Print formatted ::xbps_object_t to \a fp. + * + * Prints the ::xbps_object_t \a obj to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] obj The object to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp); + +/**@}*/ + +#endif /* !_XBPS_FMT_H_ */ From 68940b59e3cd4e6ff033b92237d93e2325c46234 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 20 Sep 2023 00:16:21 +0200 Subject: [PATCH 29/34] lib/format.c: make struct xbps_fmt private --- bin/xbps-query/list.c | 7 +- bin/xbps-query/main.c | 19 ++-- include/xbps/fmt.h | 55 ++++++----- lib/format.c | 173 ++++++++++++++++++---------------- tests/xbps/libxbps/fmt/main.c | 9 +- 5 files changed, 141 insertions(+), 122 deletions(-) diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index a70e26ad6..2b644d9e5 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -34,10 +34,13 @@ #include #include -#include "defs.h" -#include "xbps.h" +#include + +#include "xbps/fmt.h" #include "xbps/json.h" +#include "defs.h" + struct length_max_cb { const char *key; int max; diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 210e1fad3..feed6891c 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -32,10 +32,11 @@ #include -#include "defs.h" -#include "xbps.h" +#include "xbps/fmt.h" #include "xbps/json.h" +#include "defs.h" + static void __attribute__((noreturn)) usage(bool fail) { @@ -81,15 +82,15 @@ usage(bool fail) } static int -repo_fmt(FILE *fp, const struct xbps_fmt *fmt, void *data) +repo_fmt(FILE *fp, const struct xbps_fmt_var *var, void *data) { struct xbps_repo *repo = data; - if (strcmp(fmt->var, "url") == 0) { - return xbps_fmt_print_string(fmt, repo->uri, 0, fp); - } else if (strcmp(fmt->var, "signed") == 0) { - return xbps_fmt_print_string(fmt, repo->is_signed ? "true" : "false", 0, fp); - } else if (strcmp(fmt->var, "packages") == 0) { - return xbps_fmt_print_number(fmt, xbps_dictionary_count(repo->idx), fp); + if (strcmp(var->name, "url") == 0) { + return xbps_fmt_print_string(var, repo->uri, 0, fp); + } else if (strcmp(var->name, "signed") == 0) { + return xbps_fmt_print_string(var, repo->is_signed ? "true" : "false", 0, fp); + } else if (strcmp(var->name, "packages") == 0) { + return xbps_fmt_print_number(var, xbps_dictionary_count(repo->idx), fp); } return 0; } diff --git a/include/xbps/fmt.h b/include/xbps/fmt.h index 55f842981..a21dc9f95 100644 --- a/include/xbps/fmt.h +++ b/include/xbps/fmt.h @@ -15,21 +15,21 @@ /**@{*/ /** - * @struct xbps_fmt xbps.h "xbps.h" - * @brief Structure of parsed format string variable. + * @struct xbps_fmt xbps/fmt.h + * @brief Structure of parsed format string. */ -struct xbps_fmt { - /** - * @private - * @var prefix - * @brief Prefix of the format chunk. - */ - char *prefix; +struct xbps_fmt; + +/** + * @struct xbps_fmt xbps/fmt.h + * @brief Structure of a parsed format string variable. + */ +struct xbps_fmt_var { /** - * @var var + * @var name * @brief Variable name. */ - char *var; + char *name; /** * @var def * @brief Default value. @@ -48,8 +48,8 @@ struct xbps_fmt { }; /** - * @struct xbps_fmt_def xbps.h "xbps.h" - * @brief Structure of parsed format specifier. + * @struct xbps_fmt_def xbps/fmt.h + * @brief Structure of format default value. */ struct xbps_fmt_def { enum { @@ -65,7 +65,7 @@ struct xbps_fmt_def { }; /** - * @struct xbps_fmt_spec xbps.h "xbps.h" + * @struct xbps_fmt_spec xbps/fmt.h * @brief Structure of parsed format specifier. */ struct xbps_fmt_spec { @@ -121,14 +121,13 @@ struct xbps_fmt_spec { /** * @brief Format callback, called for each variable in the format string. * - * A callback function should write data associated with \a var to \a fp and use - * \a w as alignment specifier. + * The callback function should write data as specified by \a var to \a fp. * - * @param[in] fp The file to print to. - * @param[in] fmt The format specifier. + * @param[in] fp File to format to. + * @param[in] var Variable to format. * @param[in] data Userdata passed to the xbps_fmt() function. */ -typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt *fmt, void *data); +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt_var *var, void *data); /** * @brief Parses the format string \a format. @@ -215,42 +214,42 @@ int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); /** * @brief Print formatted number to \a fp. * - * Prints the number \a num to \a fp according to the specification \a spec. + * Prints the number \a num to \a fp according to the specification \a var. * - * @param[in] fmt Format specification. + * @param[in] var Variable to format. * @param[in] num Number to print. * @param[in] fp File to print to. * * @return Returns 0 on success. */ -int xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t num, FILE *fp); +int xbps_fmt_print_number(const struct xbps_fmt_var *var, int64_t num, FILE *fp); /** * @brief Print formatted string to \a fp. * - * Prints the string \a str to \a fp according to the specification \a spec. + * Prints the string \a str to \a fp according to the specification \a var. * - * @param[in] fmt Format specification. + * @param[in] var Variable to print. * @param[in] str String to print. * @param[in] len Length of the string or 0. * @param[in] fp File to print to. * * @return Returns 0 on success. */ -int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp); +int xbps_fmt_print_string(const struct xbps_fmt_var *var, const char *str, size_t len, FILE *fp); /** * @brief Print formatted ::xbps_object_t to \a fp. * - * Prints the ::xbps_object_t \a obj to \a fp according to the specification \a spec. + * Prints the ::xbps_object_t \a obj to \a fp according to the specification in \a var. * - * @param[in] spec Format specification. + * @param[in] var Variable to format. * @param[in] obj The object to print. * @param[in] fp File to print to. * * @return Returns 0 on success. */ -int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp); +int xbps_fmt_print_object(const struct xbps_fmt_var *var, xbps_object_t obj, FILE *fp); /**@}*/ diff --git a/lib/format.c b/lib/format.c index 88640504a..b14f5d39b 100644 --- a/lib/format.c +++ b/lib/format.c @@ -29,6 +29,7 @@ #include #include "compat.h" +#include "xbps/fmt.h" #include "xbps/json.h" #include "xbps/xbps_array.h" #include "xbps/xbps_bool.h" @@ -49,6 +50,17 @@ * */ +struct xbps_fmt { + /** + * @brief Prefix of the format chunk. + */ + char *prefix; + /** + * @brief Variable in the format string. + */ + struct xbps_fmt_var var; +}; + struct strbuf { size_t sz, len; char *mem; @@ -199,23 +211,23 @@ parse_d(const char **pos, int64_t *d) } static int -parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, - struct xbps_fmt_def *def_storage) +parse_default(const char **pos, struct xbps_fmt_def **defp, struct strbuf *buf) { struct strbuf buf2 = {0}; - struct xbps_fmt_def *def; + struct xbps_fmt_def *def = *defp; const char *p = *pos; char *str = NULL; int r; - if (*p++ != '?') + if (*p++ != '?') { + *defp = NULL; return 0; - if (!def_storage) { - fmt->def = def = calloc(1, sizeof(*def)); + } + + if (!def) { + def = *defp = calloc(1, sizeof(*def)); if (!def) return -errno; - } else { - fmt->def = def = def_storage; } if ((*p >= '0' && *p <= '9') || *p == '-') { @@ -351,27 +363,30 @@ parse_humanize(const char **pos, struct humanize *humanize) } static int -parse_conversion(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_conv *conv_storage) +parse_conversion(const char **pos, struct xbps_fmt_conv **convp) { + struct xbps_fmt_conv *conv = *convp; + if (**pos != '!') { - fmt->conv = NULL; + *convp = NULL; return 0; } - fmt->conv = conv_storage; - if (!conv_storage) - fmt->conv = calloc(1, sizeof(*fmt->conv)); - if (!fmt->conv) - return -errno; + + if (!conv) { + conv = *convp = calloc(1, sizeof(*conv)); + if (!conv) + return -errno; + } if (strncmp(*pos + 1, "strmode", sizeof("strmode") - 1) == 0) { *pos += sizeof("strmode"); - fmt->conv->type = STRMODE; + conv->type = STRMODE; return 0; } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { - fmt->conv->type = HUMANIZE; + conv->type = HUMANIZE; *pos += sizeof("humanize"); - return parse_humanize(pos, &fmt->conv->humanize); + return parse_humanize(pos, &conv->humanize); } else if (strncmp(*pos + 1, "json", sizeof("json") - 1) == 0) { - fmt->conv->type = JSON; + conv->type = JSON; *pos += sizeof("json"); return 0; } @@ -379,27 +394,25 @@ parse_conversion(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_conv *c } static int -parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_storage) +parse_spec(const char **pos, struct xbps_fmt_spec **specp) { bool fill = false; - struct xbps_fmt_spec *spec; + struct xbps_fmt_spec *spec = *specp; const char *p = *pos; int r; /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ if (*p != ':') { - fmt->spec = NULL; + *specp = NULL; return 0; } p++; - if (!spec_storage) { - spec = fmt->spec = calloc(1, sizeof(*fmt->spec)); - if (!fmt->spec) + if (!spec) { + spec = *specp = calloc(1, sizeof(*spec)); + if (!spec) return -errno; - } else { - spec = fmt->spec = spec_storage; } /* defaults */ @@ -464,11 +477,7 @@ parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_st } static int -parse(const char **pos, struct xbps_fmt *fmt, - struct strbuf *buf, - struct xbps_fmt_def *def_storage, - struct xbps_fmt_conv *conv_storage, - struct xbps_fmt_spec *spec_storage) +parse(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf) { const char *p = *pos; const char *e; @@ -494,26 +503,26 @@ parse(const char **pos, struct xbps_fmt *fmt, r = strbuf_puts(buf, p, e - p); if (r < 0) return r; - fmt->var = buf->mem; + fmt->var.name = buf->mem; } else { - fmt->var = strndup(p, e - p); - if (!fmt->var) + fmt->var.name = strndup(p, e - p); + if (!fmt->var.name) return -errno; } p = e; /* default ::= ['?' ...] */ - r = parse_default(&p, fmt, buf, def_storage); + r = parse_default(&p, &fmt->var.def, buf); if (r < 0) return r; /* conversion ::= ['!' ...] */ - r = parse_conversion(&p, fmt, conv_storage); + r = parse_conversion(&p, &fmt->var.conv); if (r < 0) return r; /* format_spec ::= [':' ...] */ - r = parse_spec(&p, fmt, spec_storage); + r = parse_spec(&p, &fmt->var.spec); if (r < 0) return r; @@ -557,7 +566,7 @@ xbps_fmt_parse(const char *format) goto err; } if (t == TVAR) { - r = parse(&pos, &fmt[n], NULL, NULL, NULL, NULL); + r = parse(&pos, &fmt[n], NULL); if (r < 0) goto err; } @@ -580,14 +589,14 @@ xbps_fmt_free(struct xbps_fmt *fmt) { if (!fmt) return; - for (struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + for (struct xbps_fmt *f = fmt; f->prefix || f->var.name; f++) { free(f->prefix); - free(f->var); - if (f->def && f->def->type == XBPS_FMT_DEF_STR) - free(f->def->val.str); - free(f->def); - free(f->spec); - free(f->conv); + free(f->var.name); + if (f->var.def && f->var.def->type == XBPS_FMT_DEF_STR) + free(f->var.def->val.str); + free(f->var.def); + free(f->var.spec); + free(f->var.conv); } free(fmt); } @@ -615,11 +624,16 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) struct xbps_fmt_def def = {0}; struct xbps_fmt_conv conv = {0}; struct xbps_fmt_spec spec = {0}; - struct xbps_fmt fmt = { .var = buf.mem }; - r = parse(&pos, &fmt, &buf, &def, &conv, &spec); + struct xbps_fmt fmt = { + .var.name = buf.mem, + .var.conv = &conv, + .var.def = &def, + .var.spec = &spec, + }; + r = parse(&pos, &fmt, &buf); if (r < 0) goto out; - r = cb(fp, &fmt, data); + r = cb(fp, &fmt.var, data); if (r != 0) goto out; } @@ -633,11 +647,11 @@ int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) { int r; - for (const struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + for (const struct xbps_fmt *f = fmt; f->prefix || f->var.name; f++) { if (f->prefix) fprintf(fp, "%s", f->prefix); - if (f->var) { - r = cb(fp, f, data); + if (f->var.name) { + r = cb(fp, &f->var, data); if (r != 0) return r; } @@ -646,11 +660,11 @@ xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) } int -xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp) +xbps_fmt_print_string(const struct xbps_fmt_var *var, const char *str, size_t len, FILE *fp) { - const struct xbps_fmt_spec *spec = fmt->spec; + const struct xbps_fmt_spec *spec = var->spec; - if (fmt->conv && fmt->conv->type == JSON) { + if (var->conv && var->conv->type == JSON) { struct xbps_json_printer pr = {.file = fp}; return xbps_json_print_quote(&pr, str); } @@ -670,9 +684,10 @@ xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, F } static int -humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE *fp) +humanize(const struct xbps_fmt_var *var, int64_t d, FILE *fp) { char buf[64]; + struct humanize *h = &var->conv->humanize; int scale = 0; int width = h->width ? h->width : 8; int len; @@ -691,29 +706,29 @@ humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE * len = humanize_number(buf, width, d, "B", scale, h->flags); if (len == -1) return -EINVAL; - return xbps_fmt_print_string(fmt, buf, len, fp); + return xbps_fmt_print_string(var, buf, len, fp); } static int -tostrmode(const struct xbps_fmt *fmt UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +tostrmode(const struct xbps_fmt_var *var UNUSED, int64_t d UNUSED, FILE *fp UNUSED) { return -ENOTSUP; } int -xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) +xbps_fmt_print_number(const struct xbps_fmt_var *var, int64_t d, FILE *fp) { char buf[64]; struct xbps_fmt_spec strspec = {0}; - struct xbps_fmt strfmt = { .spec = &strspec }; - struct xbps_fmt_spec *spec = fmt->spec; + struct xbps_fmt_var strfmt = { .spec = &strspec }; + struct xbps_fmt_spec *spec = var->spec; const char *p = buf; int len; - if (fmt->conv) { - switch (fmt->conv->type) { - case HUMANIZE: return humanize(&fmt->conv->humanize, fmt, d, fp); - case STRMODE: return tostrmode(fmt, d, fp); + if (var->conv) { + switch (var->conv->type) { + case HUMANIZE: return humanize(var, d, fp); + case STRMODE: return tostrmode(var, d, fp); case JSON: break; } } @@ -745,31 +760,31 @@ xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) } int -xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) +xbps_fmt_print_object(const struct xbps_fmt_var *var, xbps_object_t obj, FILE *fp) { - if (fmt->conv && fmt->conv->type == JSON) { + if (var->conv && var->conv->type == JSON) { struct xbps_json_printer pr = {.file = fp}; return xbps_json_print_xbps_object(&pr, obj); } switch (xbps_object_type(obj)) { case XBPS_TYPE_BOOL: - return xbps_fmt_print_string(fmt, xbps_bool_true(obj) ? "true" : "false", 0, fp); + return xbps_fmt_print_string(var, xbps_bool_true(obj) ? "true" : "false", 0, fp); case XBPS_TYPE_NUMBER: - return xbps_fmt_print_number(fmt, xbps_number_integer_value(obj), fp); + return xbps_fmt_print_number(var, xbps_number_integer_value(obj), fp); case XBPS_TYPE_STRING: - return xbps_fmt_print_string(fmt, xbps_string_cstring_nocopy(obj), + return xbps_fmt_print_string(var, xbps_string_cstring_nocopy(obj), xbps_string_size(obj), fp); case XBPS_TYPE_UNKNOWN: - if (fmt->def) { - struct xbps_fmt_def *def = fmt->def; - switch (fmt->def->type) { + if (var->def) { + struct xbps_fmt_def *def = var->def; + switch (var->def->type) { case XBPS_FMT_DEF_BOOL: - return xbps_fmt_print_string(fmt, def->val.boolean ? + return xbps_fmt_print_string(var, def->val.boolean ? "true" : "false", 0, fp); case XBPS_FMT_DEF_STR: - return xbps_fmt_print_string(fmt, def->val.str, 0, fp); + return xbps_fmt_print_string(var, def->val.str, 0, fp); case XBPS_FMT_DEF_NUM: - return xbps_fmt_print_number(fmt, def->val.num, fp); + return xbps_fmt_print_number(var, def->val.num, fp); } } default: @@ -783,11 +798,11 @@ struct fmt_dict_ctx { }; static int -fmt_dict_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) +fmt_dict_cb(FILE *fp, const struct xbps_fmt_var *var, void *data) { struct fmt_dict_ctx *ctx = data; - xbps_object_t obj = xbps_dictionary_get(ctx->dict, fmt->var); - return xbps_fmt_print_object(fmt, obj, fp); + xbps_object_t obj = xbps_dictionary_get(ctx->dict, var->name); + return xbps_fmt_print_object(var, obj, fp); } int diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c index 40298a98e..ac9ddfb84 100644 --- a/tests/xbps/libxbps/fmt/main.c +++ b/tests/xbps/libxbps/fmt/main.c @@ -34,6 +34,7 @@ #include #include +#include ATF_TC(xbps_fmt_print_number); @@ -71,10 +72,10 @@ ATF_TC_BODY(xbps_fmt_print_number, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { - struct xbps_fmt fmt = { .spec = &tests[i].spec }; + struct xbps_fmt_var var = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_print_number(&fmt, tests[i].d, fp); + xbps_fmt_print_number(&var, tests[i].d, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } @@ -109,10 +110,10 @@ ATF_TC_BODY(xbps_fmt_print_string, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { - struct xbps_fmt fmt = { .spec = &tests[i].spec }; + struct xbps_fmt_var var = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_print_string(&fmt, tests[i].input, tests[i].len, fp); + xbps_fmt_print_string(&var, tests[i].input, tests[i].len, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } From 4ec4a3c0e1a1856b28c463eaecfbb071a5e27eb9 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 20 Sep 2023 00:24:01 +0200 Subject: [PATCH 30/34] bin/xbps-query: add pkgname,version and revision format variables --- bin/xbps-query/list.c | 58 ++++++++++++++++++++++++++++++++----- bin/xbps-query/xbps-query.1 | 10 +++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index 2b644d9e5..48ddba3d3 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -137,31 +137,73 @@ list_pkgs_pkgdb(struct xbps_handle *xhp) return xbps_pkgdb_foreach_cb(xhp, list_pkgs_pkgdb_cb, &lpc); } -struct list_pkgdb_cb { +struct list_pkgdb_ctx { struct xbps_fmt *fmt; struct xbps_json_printer *json; int (*filter)(xbps_object_t obj); }; +static int +fmt_pkg_cb(FILE *fp, const struct xbps_fmt_var *var, void *data) +{ + const char *pkgver = NULL; + xbps_dictionary_t pkgd = data; + xbps_object_t obj; + + obj = xbps_dictionary_get(pkgd, var->name); + if (obj) + return xbps_fmt_print_object(var, obj, fp); + + if (!xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver)) { + xbps_error_printf("invalid package: missing `pkgver`\n"); + return -EINVAL; + } + + if (strcmp(var->name, "pkgname") == 0) { + char pkgname[XBPS_NAME_SIZE]; + if (xbps_pkg_name(pkgname, sizeof(pkgname), pkgver)) { + xbps_error_printf("invalid `pkgver`: %s\n", pkgver); + return -EINVAL; + } + return xbps_fmt_print_string(var, pkgname, 0, fp); + } else if (strcmp(var->name, "version") == 0) { + const char *version = xbps_pkg_version(pkgver); + if (!version) { + xbps_error_printf("invalid `pkgver`: %s\n", pkgver); + return -EINVAL; + } + return xbps_fmt_print_string(var, version, 0, fp); + } else if (strcmp(var->name, "revision") == 0) { + const char *revision = xbps_pkg_revision(pkgver); + if (!revision) { + xbps_error_printf("invalid `pkgver`: %s\n", pkgver); + return -EINVAL; + } + return xbps_fmt_print_string(var, revision, 0, fp); + } + + return 0; +} + static int list_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, const char *key UNUSED, void *arg, bool *loop_done UNUSED) { - struct list_pkgdb_cb *ctx = arg; + struct list_pkgdb_ctx *ctx = arg; int r; if (ctx->filter) { r = ctx->filter(obj); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; } if (ctx->fmt) { - r = xbps_fmt_dictionary(ctx->fmt, obj, stdout); + r = xbps_fmt(ctx->fmt, &fmt_pkg_cb, obj, stdout); } else if (ctx->json) { r = xbps_json_print_xbps_object(ctx->json, obj); + if (r < 0) + return r; fprintf(ctx->json->file, "\n"); } else { r = -ENOTSUP; @@ -172,7 +214,7 @@ list_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, int list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *format, int json) { - struct list_pkgdb_cb ctx = {.filter = filter}; + struct list_pkgdb_ctx ctx = {.filter = filter}; struct xbps_json_printer pr = {0}; int r; if (json > 0) { @@ -237,7 +279,7 @@ list_orphans(struct xbps_handle *xhp, const char *format) xbps_object_t obj = xbps_array_get(orphans, i); if (!obj) return -errno; - r = xbps_fmt_dictionary(fmt, obj, stdout); + r = xbps_fmt(fmt, &fmt_pkg_cb, obj, stdout); if (r < 0) goto err; } diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index e4f9533da..e931a808e 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -460,6 +460,16 @@ installation state of the package. .It Ic tags list of categories the package is associated with. .El +.Pp +Additional dynamic properties are available for +.Sx FORMAT STRINGS . +.Pp +.Bl -tag -compact -width 17m +.It Ic version +version of the package. +.It Ic revision +revision of the package. +.El .Sh ENVIRONMENT .Bl -tag -width XBPS_TARGET_ARCH .It Sy XBPS_ARCH From c02eb41c4bc630dca0822714e025c5098df885d6 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 20 Sep 2023 01:17:19 +0200 Subject: [PATCH 31/34] lib/json.c: names and documentation --- include/xbps/json.h | 109 ++++++++++++++++++++++++++++++++- lib/format.c | 2 +- lib/json.c | 13 ++-- tests/xbps/libxbps/json/main.c | 6 +- 4 files changed, 117 insertions(+), 13 deletions(-) diff --git a/include/xbps/json.h b/include/xbps/json.h index 1264d3836..b00b8c9f1 100644 --- a/include/xbps/json.h +++ b/include/xbps/json.h @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck */ /* SPDX-License-Identifier: BSD-2-Clause */ + #ifndef _XBPS_JSON_H_ #define _XBPS_JSON_H_ @@ -13,22 +14,126 @@ #include #include +/** @addtogroup json */ +/**@{*/ + +/** + * @struct xbps_json_printer xbps/json.h + * @brief Structure holding state while printing json. + */ struct xbps_json_printer { + /** + * @var file + * @brief Output file to print to. + */ FILE *file; + /** + * @var depth + * @brief The current depth inside objects or arrays. + */ unsigned depth; + /** + * @var indent + * @brief Number of indent spaces per depth. + */ uint8_t indent; + /** + * @var compact + * @brief Compact mode removes unnecessary spaces. + */ bool compact; }; -int xbps_json_print_escape(struct xbps_json_printer *p, const char *s); -int xbps_json_print_quote(struct xbps_json_printer *p, const char *s); +/** + * @brief Escape and write the string \a s to the json file. + * + * @param[in] p Json print context. + * @param[in] s The string to write. + * + * @return 0 on success or a negative errno from fprintf(3). + */ +int xbps_json_print_escaped(struct xbps_json_printer *p, const char *s); + +/** + * @brief Write the string \a s as quoted string to the json file. + * + * @param[in] p Json print context. + * @param[in] s The string to write. + * + * @return 0 on success or a negative errno from fprintf(3). + */ +int xbps_json_print_quoted(struct xbps_json_printer *p, const char *s); + +/** + * @brief Write boolean to the json stream. + * + * @param[in] p Json print context. + * @param[in] b Boolean value. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_bool(struct xbps_json_printer *p, bool b); +/** + * @brief Write a ::xbps_string_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] str String value to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_string(struct xbps_json_printer *p, xbps_string_t str); + +/** + * @brief Write a ::xbps_number_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] num Number value to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_number(struct xbps_json_printer *p, xbps_number_t num); + +/** + * @brief Write a ::xbps_boolean_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] b Boolean value to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_boolean(struct xbps_json_printer *p, xbps_bool_t b); + +/** + * @brief Write a ::xbps_array_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] array Array to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_array(struct xbps_json_printer *p, xbps_array_t array); + +/** + * @brief Write a ::xbps_dictionary_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] dict Dictionary to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_dictionary(struct xbps_json_printer *p, xbps_dictionary_t dict); + +/** + * @brief Write a ::xbps_object_t to the json stream. + * + * @param[in] p Json print context. + * @param[in] obj Object to print. + * + * @return 0 on success or a negative errno from fprintf(3). + */ int xbps_json_print_xbps_object(struct xbps_json_printer *p, xbps_object_t obj); +/**@}*/ + #endif /* !_XBPS_JSON_H_ */ diff --git a/lib/format.c b/lib/format.c index b14f5d39b..a4362299c 100644 --- a/lib/format.c +++ b/lib/format.c @@ -666,7 +666,7 @@ xbps_fmt_print_string(const struct xbps_fmt_var *var, const char *str, size_t le if (var->conv && var->conv->type == JSON) { struct xbps_json_printer pr = {.file = fp}; - return xbps_json_print_quote(&pr, str); + return xbps_json_print_quoted(&pr, str); } if (len == 0) diff --git a/lib/json.c b/lib/json.c index 75e7d0af9..ada02018b 100644 --- a/lib/json.c +++ b/lib/json.c @@ -10,6 +10,7 @@ #include #include +#include "xbps/json.h" #include "xbps/xbps_array.h" #include "xbps/xbps_bool.h" #include "xbps/xbps_dictionary.h" @@ -17,8 +18,6 @@ #include "xbps/xbps_object.h" #include "xbps/xbps_string.h" -#include "xbps/json.h" - static int __attribute__ ((format (printf, 2, 3))) xbps_json_printf(struct xbps_json_printer *p, const char *fmt, ...) { @@ -32,7 +31,7 @@ xbps_json_printf(struct xbps_json_printer *p, const char *fmt, ...) } int -xbps_json_print_escape(struct xbps_json_printer *p, const char *s) +xbps_json_print_escaped(struct xbps_json_printer *p, const char *s) { int r = 0; for (; r >= 0 && *s; s++) { @@ -56,12 +55,12 @@ xbps_json_print_escape(struct xbps_json_printer *p, const char *s) } int -xbps_json_print_quote(struct xbps_json_printer *p, const char *s) +xbps_json_print_quoted(struct xbps_json_printer *p, const char *s) { int r; if ((r = xbps_json_printf(p, "\"")) < 0) return r; - if ((r = xbps_json_print_escape(p, s)) < 0) + if ((r = xbps_json_print_escaped(p, s)) < 0) return r; return xbps_json_printf(p, "\""); } @@ -75,7 +74,7 @@ xbps_json_print_bool(struct xbps_json_printer *p, bool b) int xbps_json_print_xbps_string(struct xbps_json_printer *p, xbps_string_t str) { - return xbps_json_print_quote(p, xbps_string_cstring_nocopy(str)); + return xbps_json_print_quoted(p, xbps_string_cstring_nocopy(str)); } int @@ -164,7 +163,7 @@ xbps_json_print_xbps_dictionary(struct xbps_json_printer *p, xbps_dictionary_t d } key = xbps_dictionary_keysym_cstring_nocopy(keysym); - if ((r = xbps_json_print_quote(p, key)) < 0) + if ((r = xbps_json_print_quoted(p, key)) < 0) goto err; if ((r = xbps_json_printf(p, "%s", key_sep)) < 0) goto err; diff --git a/tests/xbps/libxbps/json/main.c b/tests/xbps/libxbps/json/main.c index 37b5fce0c..5239cbc37 100644 --- a/tests/xbps/libxbps/json/main.c +++ b/tests/xbps/libxbps/json/main.c @@ -27,19 +27,19 @@ ATF_TC_BODY(xbps_json_print_escape, tc) ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz)); - ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\"\\\b\f\n\r\t")); + ATF_CHECK_EQ(0, xbps_json_print_escaped(&p, "\"\\\b\f\n\r\t")); ATF_REQUIRE_EQ(0, fflush(p.file)); ATF_CHECK_STREQ("\\\"\\\\\\b\\f\\n\\r\\t", buf); memset(buf, '\0', bufsz); ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET)); - ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "09azAZ !$#%^()%")); + ATF_CHECK_EQ(0, xbps_json_print_escaped(&p, "09azAZ !$#%^()%")); ATF_REQUIRE_EQ(0, fflush(p.file)); ATF_CHECK_STREQ("09azAZ !$#%^()%", buf); memset(buf, '\0', bufsz); ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET)); - ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\x01\x1F")); + ATF_CHECK_EQ(0, xbps_json_print_escaped(&p, "\x01\x1F")); ATF_REQUIRE_EQ(0, fflush(p.file)); ATF_CHECK_STREQ("\\u0001\\u001f", buf); } From fa46567d75a4109ad1a21f6e7b703f73af5c9a00 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 20 Sep 2023 01:53:15 +0200 Subject: [PATCH 32/34] bin/xbps-query: bring back default/null values for package formats --- bin/xbps-query/list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index 48ddba3d3..6fda565ee 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -182,7 +182,7 @@ fmt_pkg_cb(FILE *fp, const struct xbps_fmt_var *var, void *data) return xbps_fmt_print_string(var, revision, 0, fp); } - return 0; + return xbps_fmt_print_object(var, NULL, fp); } static int From 6a88c7b72b7f2cecc384a90ffbb1db48f3169e79 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Mon, 9 Oct 2023 14:35:23 -0400 Subject: [PATCH 33/34] lib: add ability to template repo urls with {arch} {arch} will be replaced with XBPS_TARGET_ARCH (if set) or XBPS_ARCH --- data/xbps.d.5 | 8 ++++++ include/xbps_api_impl.h | 1 + lib/initend.c | 9 +++++++ lib/repo.c | 60 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/data/xbps.d.5 b/data/xbps.d.5 index 9d2d1063e..ee172b373 100644 --- a/data/xbps.d.5 +++ b/data/xbps.d.5 @@ -111,12 +111,20 @@ argument accepts local and remote repositories. A complete url or absolute path to the directory that stores the .Em -repodata archive is expected. +If the +.Ar url +contains +.Ar {arch} , +.Ar {arch} +will be replaced by the current target XBPS architecture at runtime. +If the target architecture is not set, the native architecture will be used. Note that remote repositories must be signed using .Xr xbps-rindex 1 , example: .Pp .Bl -tag -compact -width repository=https://repo-default.voidlinux.org/current .It Sy repository=https://repo-default.voidlinux.org/current +.It Sy repository=https://repo-default.voidlinux.org/current/{arch} .It Sy repository=/hostdir/binpkgs .El .It Sy rootdir=path diff --git a/include/xbps_api_impl.h b/include/xbps_api_impl.h index aac5b11b5..4282e4197 100644 --- a/include/xbps_api_impl.h +++ b/include/xbps_api_impl.h @@ -99,6 +99,7 @@ int HIDDEN xbps_transaction_fetch(struct xbps_handle *, int HIDDEN xbps_transaction_pkg_deps(struct xbps_handle *, xbps_array_t, xbps_dictionary_t); int HIDDEN xbps_transaction_internalize(struct xbps_handle *, xbps_object_iterator_t); +char HIDDEN *repo_format(struct xbps_handle *, const char *); char HIDDEN *xbps_get_remote_repo_string(const char *); int HIDDEN xbps_repo_sync(struct xbps_handle *, const char *); int HIDDEN xbps_file_hash_check_dictionary(struct xbps_handle *, diff --git a/lib/initend.c b/lib/initend.c index 547db6a98..f3d88b9a8 100644 --- a/lib/initend.c +++ b/lib/initend.c @@ -164,6 +164,15 @@ xbps_init(struct xbps_handle *xhp) xhp->flags |= XBPS_FLAG_DISABLE_SYSLOG; } + for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { + const char *url = NULL; + xbps_array_get_cstring_nocopy(xhp->repositories, i, &url); + xbps_array_set_cstring_nocopy(xhp->repositories, i, repo_format(xhp, url)); + } + + xbps_dbg_printf("Native architecture is %s\n", xhp->native_arch); + xbps_dbg_printf("Target architecture is %s\n", xhp->target_arch); + if (xhp->flags & XBPS_FLAG_DEBUG) { const char *repodir; for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { diff --git a/lib/repo.c b/lib/repo.c index 8d2aa6bd1..611e09008 100644 --- a/lib/repo.c +++ b/lib/repo.c @@ -40,6 +40,7 @@ #include #include +#include "xbps/fmt.h" #include "xbps_api_impl.h" /** @@ -196,6 +197,65 @@ repo_open_remote(struct xbps_repo *repo) return rv; } +static int +repo_fmt(FILE *fp, const struct xbps_fmt_var *var, void *data) +{ + struct xbps_handle *xhp = data; + const char *arch; + + if (xhp->target_arch) + arch = xhp->target_arch; + else + arch = xhp->native_arch; + + if (strcmp(var->name, "arch") == 0) { + return xbps_fmt_print_string(var, arch, 0, fp); + } + return 0; +} + +char * +repo_format(struct xbps_handle *xhp, const char *url) +{ + struct xbps_fmt *fmt = NULL; + FILE *fmt_stream; + char *fmt_buf; + size_t len; + int r; + + assert(xhp); + assert(url); + + if (!strstr(url, "{arch}")) + return strdup(url); + + xbps_dbg_printf("Processing templated repository: %s\n", url); + + fmt_stream = open_memstream(&fmt_buf, &len); + if (!fmt_stream) { + xbps_error_printf("failed to open buffer: %s\n", strerror(errno)); + goto fmtout; + } + fmt = xbps_fmt_parse(url); + if (!fmt) { + xbps_error_printf("failed to parse format for repo '%s': %s\n", url, strerror(errno)); + goto fmtout; + } + r = xbps_fmt(fmt, repo_fmt, xhp, fmt_stream); + if (r < 0) { + xbps_error_printf("failed to format repo '%s': %s\n", url, strerror(-r)); + goto fmtout; + } + fflush(fmt_stream); + return fmt_buf; + +fmtout: + fclose(fmt_stream); + xbps_fmt_free(fmt); + free(fmt_buf); + return NULL; +} + static struct xbps_repo * repo_open_with_type(struct xbps_handle *xhp, const char *url, const char *name) { From 441c728ade30d62f7395aa23a0713fc1106ee8d0 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Tue, 5 Dec 2023 05:19:50 -0500 Subject: [PATCH 34/34] data/repod-main.conf: template arch --- data/repod-main.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/repod-main.conf b/data/repod-main.conf index e1c99f1e3..d97352243 100644 --- a/data/repod-main.conf +++ b/data/repod-main.conf @@ -1 +1 @@ -repository=https://repo-default.voidlinux.org/current +repository=https://repo-default.voidlinux.org/current/{arch}