Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
(r)query: if %{ is encountered fallback on using directly pkg_printf(3)
Baptiste Daroussin committed 19 days ago
commit 5a0342c5601a18e175e0c1e4b167e327311f4bb3
parent 2601cc1
9 files changed +265 -6
modified docs/pkg-query.8
@@ -14,7 +14,7 @@
.\"
.\"     @(#)pkg.8
.\"
-
.Dd December 13, 2025
+
.Dd April 17, 2026
.Dt PKG-QUERY 8
.Os
.Sh NAME
@@ -293,6 +293,64 @@ stands for the annotation tag, and
.Cm v
stands for the annotation value.
.El
+
.Ss Inline iteration with \&%{...\&%}
+
.Pp
+
When the format string contains a
+
.Cm \&%{...\&%}
+
construct, the entire format is interpreted using
+
.Xr pkg_printf 3
+
format codes instead of the legacy query format codes.
+
This allows multiline items to be joined on a single line using a
+
custom separator, rather than producing one line per item.
+
.Pp
+
The syntax is:
+
.Bd -literal -offset indent
+
%X%{<item-format>%|<separator>%}
+
.Ed
+
.Pp
+
Where
+
.Cm X
+
is a list-valued format code
+
.Pq Cm A , B , C , D , F , G , L , O , U , d , r , b , y , Y ,
+
.Cm <item-format>
+
is the format for each item, and
+
.Cm <separator>
+
.Pq delimited by Cm \&%|
+
is optional and inserted between items.
+
.Pp
+
Inside
+
.Cm \&%{...\&%} ,
+
use
+
.Xr pkg_printf 3
+
sub-option codes:
+
.Cm \&%On
+
for option name
+
.Pq Cm \&%Ok No is accepted as a deprecated alias ,
+
.Cm \&%Ov
+
for option value,
+
.Cm \&%An
+
for annotation name
+
.Pq Cm \&%At No is accepted as a deprecated alias ,
+
.Cm \&%Fn
+
for file path,
+
.Cm \&%Fp
+
for file permissions,
+
.Cm \&%Cn
+
for category name, etc.
+
See
+
.Xr pkg_printf 3
+
for the full list of format codes.
+
.Pp
+
.Sy Note :
+
When using
+
.Cm \&%{...\&%} ,
+
the format codes outside the braces must also follow
+
.Xr pkg_printf 3
+
conventions.
+
In particular,
+
.Cm \&%Fn
+
is used for file path
+
.Pq not Cm \&%Fp No as in legacy query format .
.Sh EVALUATION FORMAT
Packages can be selected by using expressions comparing
.Ar Variables
@@ -462,6 +520,15 @@ List unmaintained packages:
.Pp
List packages that depend on python312:
.Dl $ pkg query -e '%dn = python312' %o
+
.Pp
+
List all categories of a package on a single line, separated by spaces:
+
.Dl $ pkg query '%n: %C%{%Cn%| %}' firefox
+
.Pp
+
List all shared libraries used by a package on a single line:
+
.Dl $ pkg query '%n: %B%{%Bn%| %}' perl
+
.Pp
+
List all options of a package in key=value format:
+
.Dl $ pkg query '%n %O%{%On=%Ov%| %}' vim
.Sh SEE ALSO
.Xr pkg_create 3 ,
.Xr pkg_printf 3 ,
modified docs/pkg-rquery.8
@@ -14,7 +14,7 @@
.\"
.\"     @(#)pkg.8
.\"
-
.Dd July 7, 2024
+
.Dd April 17, 2026
.Dt PKG-RQUERY 8
.Os
.Sh NAME
@@ -245,6 +245,17 @@ stands for the annotation tag, and
.Cm v
stands for the annotation value.
.El
+
.Ss Inline iteration with \&%{...\&%}
+
.Pp
+
When the format string contains a
+
.Cm \&%{...\&%}
+
construct, the entire format is interpreted using
+
.Xr pkg_printf 3
+
format codes.
+
This allows multiline items to be joined on a single line.
+
See
+
.Xr pkg-query 8
+
for full details and examples.
.Sh EVALUATION FORMAT
.Ss Variables
.Bl -tag -width Ds
modified docs/pkg_printf.3
@@ -458,6 +458,10 @@ Default row format
.It Cm \^%An
Annotation tag name [string]
.Vt struct pkg_note *
+
.It Cm \^%At
+
Deprecated alias for
+
.Cm %An .
+
.Vt struct pkg_note *
.It Cm \^%Av
Annotation value [string]
.Vt struct pkg_note *
@@ -568,6 +572,10 @@ Default row format:
.It Cm %On
Option name [string]
.Vt struct pkg_option *
+
.It Cm %Ok
+
Deprecated alias for
+
.Cm %On .
+
.Vt struct pkg_option *
.It Cm %Ov
Option value [string]
.Vt struct pkg_option *
modified libpkg/libpkg.ver
@@ -54,6 +54,7 @@ global:
	pkg_files;
	pkg_finish_repo;
	pkg_fprintf;
+
	pkg_fprintf_pkg;
	pkg_free;
	pkg_get_b;
	pkg_get_cachedir;
modified libpkg/pkg.h.in
@@ -1641,6 +1641,13 @@ int pkg_vprintf(const char * restrict format, va_list ap);
int pkg_fprintf(FILE * restrict stream, const char * restrict format, ...);

/**
+
 * Variant of pkg_fprintf that uses the same pkg for every format code,
+
 * instead of consuming one va_arg per code.  This is useful for query-style
+
 * output where a single format string refers to one package.
+
 */
+
int pkg_fprintf_pkg(FILE * restrict stream, const char * restrict format, struct pkg *pkg);
+

+
/**
 * print to named stream from pkg as indicated by the format code format
 * @param ap varargs arglist
 * @param format String with embedded %-escapes indicating what to output
modified libpkg/pkg_printf.c
@@ -827,6 +827,24 @@ static const struct pkg_printf_fmt fmt[] = {
		PP_ALL,
		&format_int_checksum,
	},
+
	[PP_PKG_OPTION_KEY] =
+
	{
+
		'O',
+
		'k',
+
		false,
+
		false,
+
		PP_PKG|PP_O,
+
		&format_option_name,
+
	},
+
	[PP_PKG_ANNOTATION_TAG] =
+
	{
+
		'A',
+
		't',
+
		false,
+
		false,
+
		PP_PKG|PP_A,
+
		&format_annotation_name,
+
	},
	[PP_LITERAL_PERCENT] =
	{
		'%',
@@ -3270,6 +3288,74 @@ pkg_xstring_vprintf(xstring * restrict buf, const char * restrict format,
	free_percent_esc(p);
	return (buf);
}
+
/**
+
 * Format data from a single pkg into buf, using the same pkg pointer
+
 * for every format code.  Unlike pkg_xstring_vprintf which consumes
+
 * one va_arg per format code, this always uses the supplied pkg.
+
 */
+
static xstring *
+
pkg_xstring_printf_set(xstring * restrict buf,
+
    const char * restrict format, struct pkg *pkg)
+
{
+
	const char		*f, *fend;
+
	struct percent_esc	*p;
+

+
	assert(buf != NULL);
+
	assert(format != NULL);
+

+
	f = format;
+
	p = new_percent_esc();
+

+
	if (p == NULL) {
+
		xstring_reset(buf);
+
		return (buf);
+
	}
+

+
	while ( *f != '\0' ) {
+
		switch(*f) {
+
		case '%':
+
			fend = parse_format(f, PP_PKG, p);
+
			f = process_format_main(buf, p, f, fend, pkg);
+
			break;
+
		case '\\':
+
			f = process_escape(buf, f);
+
			break;
+
		default:
+
			fputc(*f, buf->fp);
+
			f++;
+
			break;
+
		}
+
		if (f == NULL) {
+
			xstring_reset(buf);
+
			break;
+
		}
+
	}
+

+
	free_percent_esc(p);
+
	return (buf);
+
}
+

+
int
+
pkg_fprintf_pkg(FILE * restrict stream, const char * restrict format,
+
    struct pkg *pkg)
+
{
+
	xstring	*buf;
+
	int	 count;
+

+
	buf = xstring_new();
+

+
	if (buf)
+
		buf = pkg_xstring_printf_set(buf, format, pkg);
+
	fflush(buf->fp);
+
	if (buf && strlen(buf->buf) > 0) {
+
		count = fprintf(stream, "%s", buf->buf);
+
	} else
+
		count = -1;
+
	if (buf)
+
		xstring_free(buf);
+

+
	return (count);
+
}
/*
 * That's All Folks!
 */
modified libpkg/private/pkg_printf.h
@@ -150,7 +150,9 @@ typedef enum _fmt_code_t {
	PP_PKG_PROVIDED_NAME,
	PP_PKG_SHORT_CHECKSUM,
	PP_PKG_INT_CHECKSUM,
-
	PP_LAST_FORMAT = PP_PKG_INT_CHECKSUM,
+
	PP_PKG_OPTION_KEY,		/* %Ok alias for %On (deprecated) */
+
	PP_PKG_ANNOTATION_TAG,		/* %At alias for %An (deprecated) */
+
	PP_LAST_FORMAT = PP_PKG_ANNOTATION_TAG,
	PP_LITERAL_PERCENT,
	PP_UNKNOWN,
	PP_END_MARKER
modified src/query.c
@@ -413,6 +413,12 @@ print_query(struct pkg *pkg, char *qstr, char multiline)
	struct pkg_kvlist	*kl = NULL;
	struct pkg_kvlist_iterator	*kit;

+
	if (multiline == '{') {
+
		pkg_fprintf_pkg(stdout, qstr, pkg);
+
		putchar('\n');
+
		return;
+
	}
+

	output = xstring_new();
	bool printed = false;

@@ -1132,8 +1138,46 @@ exec_query(int argc, char **argv)
		goto cleanup;
	}

-
	if (analyse_query_string(argv[0], accepted_query_flags, q_flags_len,
-
			&query_flags, &multiline) != EPKG_OK) {
+
	if (strstr(argv[0], "%{") != NULL) {
+
		/*
+
		 * Format string uses pkg_printf %{..%} syntax.
+
		 * Delegate entirely to pkg_fprintf_pkg and extract
+
		 * PKG_LOAD flags from the main format characters.
+
		 */
+
		multiline = '{';
+
		for (const char *p = argv[0]; *p != '\0'; p++) {
+
			if (*p != '%')
+
				continue;
+
			p++;
+
			/* skip %{, %|, %}, %% */
+
			if (*p == '{' || *p == '|' || *p == '}' ||
+
			    *p == '%' || *p == '\0')
+
				continue;
+
			/* skip field width/modifiers */
+
			while (*p == '#' || *p == '?' || *p == '-' ||
+
			    (*p >= '0' && *p <= '9'))
+
				p++;
+
			switch (*p) {
+
			case 'A': query_flags |= PKG_LOAD_ANNOTATIONS; break;
+
			case 'B': query_flags |= PKG_LOAD_SHLIBS_REQUIRED; break;
+
			case 'C': query_flags |= PKG_LOAD_CATEGORIES; break;
+
			case 'D': query_flags |= PKG_LOAD_DIRS; break;
+
			case 'F': query_flags |= PKG_LOAD_FILES; break;
+
			case 'G': query_flags |= PKG_LOAD_GROUPS; break;
+
			case 'L': query_flags |= PKG_LOAD_LICENSES; break;
+
			case 'O': query_flags |= PKG_LOAD_OPTIONS; break;
+
			case 'U': query_flags |= PKG_LOAD_USERS; break;
+
			case 'd': query_flags |= PKG_LOAD_DEPS; break;
+
			case 'r': query_flags |= PKG_LOAD_RDEPS; break;
+
			case 'b': query_flags |= PKG_LOAD_SHLIBS_PROVIDED; break;
+
			case 'y': query_flags |= PKG_LOAD_PROVIDES; break;
+
			case 'Y': query_flags |= PKG_LOAD_REQUIRES; break;
+
			case 'X': query_flags |= PKG_LOAD_BASIC |
+
			    PKG_LOAD_SCRIPTS | PKG_LOAD_LUA_SCRIPTS; break;
+
			}
+
		}
+
	} else if (analyse_query_string(argv[0], accepted_query_flags,
+
	    q_flags_len, &query_flags, &multiline) != EPKG_OK) {
		retcode = EXIT_FAILURE;
		goto cleanup;
	}
modified src/rquery.c
@@ -197,7 +197,40 @@ exec_rquery(int argc, char **argv)
			match = MATCH_ALL;
	}

-
	if (!index_output && analyse_query_string(argv[0], accepted_rquery_flags, q_flags_len, &query_flags, &multiline) != EPKG_OK) {
+
	if (!index_output && strstr(argv[0], "%{") != NULL) {
+
		multiline = '{';
+
		for (const char *p = argv[0]; *p != '\0'; p++) {
+
			if (*p != '%')
+
				continue;
+
			p++;
+
			if (*p == '{' || *p == '|' || *p == '}' ||
+
			    *p == '%' || *p == '\0')
+
				continue;
+
			while (*p == '#' || *p == '?' || *p == '-' ||
+
			    (*p >= '0' && *p <= '9'))
+
				p++;
+
			switch (*p) {
+
			case 'A': query_flags |= PKG_LOAD_ANNOTATIONS; break;
+
			case 'B': query_flags |= PKG_LOAD_SHLIBS_REQUIRED; break;
+
			case 'C': query_flags |= PKG_LOAD_CATEGORIES; break;
+
			case 'D': query_flags |= PKG_LOAD_DIRS; break;
+
			case 'F': query_flags |= PKG_LOAD_FILES; break;
+
			case 'G': query_flags |= PKG_LOAD_GROUPS; break;
+
			case 'L': query_flags |= PKG_LOAD_LICENSES; break;
+
			case 'O': query_flags |= PKG_LOAD_OPTIONS; break;
+
			case 'U': query_flags |= PKG_LOAD_USERS; break;
+
			case 'd': query_flags |= PKG_LOAD_DEPS; break;
+
			case 'r': query_flags |= PKG_LOAD_RDEPS; break;
+
			case 'b': query_flags |= PKG_LOAD_SHLIBS_PROVIDED; break;
+
			case 'y': query_flags |= PKG_LOAD_PROVIDES; break;
+
			case 'Y': query_flags |= PKG_LOAD_REQUIRES; break;
+
			case 'X': query_flags |= PKG_LOAD_BASIC |
+
			    PKG_LOAD_SCRIPTS | PKG_LOAD_LUA_SCRIPTS; break;
+
			}
+
		}
+
	} else if (!index_output && analyse_query_string(argv[0],
+
	    accepted_rquery_flags, q_flags_len, &query_flags,
+
	    &multiline) != EPKG_OK) {
		vec_free(&reponames);
		return (EXIT_FAILURE);
	}