Skip to content

Commit

Permalink
Implement --json query format
Browse files Browse the repository at this point in the history
The existing --xml output is extremely useful when inspecting oddities,
but the format is such an eyesore. JSON is much saner to look at.
  • Loading branch information
pmatilai committed Feb 19, 2024
1 parent 4388218 commit 22d6281
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 1 deletion.
6 changes: 5 additions & 1 deletion docs/man/rpm.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ query-options

General: \[**\--changelog**\] \[**\--changes**\] \[**\--dupes**\]
\[**-i,\--info**\] \[**\--last**\] \[**\--qf,\--queryformat
***QUERYFMT*\] \[**\--xml**\]
***QUERYFMT*\] \[**\--xml**\] \[**\--json**\]

Dependencies: \[**\--conflicts**\] \[**\--enhances**\]
\[**\--obsoletes**\] \[**\--provides**\] \[**\--recommends**\]
Expand Down Expand Up @@ -552,6 +552,10 @@ Alternate output formats may be requested by following the tag with
: Human readable number (in SI). The suffix K = 1000, M = 1000000,
\...

**:json**

: Wrap data in JSON.

**:perms**

: Format file permissions.
Expand Down
1 change: 1 addition & 0 deletions include/rpm/rpmtd.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ typedef enum rpmtdFormats_e {
RPMTD_FORMAT_HUMANIEC = 21, /* human readable value, K = 1024 (int types) */
RPMTD_FORMAT_TAGNAME = 22, /* tag name (any type) */
RPMTD_FORMAT_TAGNUM = 23, /* tag number (any type) */
RPMTD_FORMAT_JSON = 24, /* json format (any type) */
} rpmtdFormats;

/** \ingroup rpmtd
Expand Down
77 changes: 77 additions & 0 deletions lib/formats.c
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,81 @@ static char * xmlFormat(rpmtd td, char **emsg)
return val;
}

static char *jsonEscape(const char *s)
{
char *es = NULL;
rstrcat(&es, "\"");
for (const char *c = s; *c != '\0'; c++) {
const char *ec = NULL;
switch (*c) {
case '\b':
ec = "\\b";
break;
case '\f':
ec = "\\f";
break;
case '\n':
ec = "\\n";
break;
case '\r':
ec = "\\r";
break;
case '\t':
ec = "\\t";
break;
case '"':
ec = "\\\"";
break;
case '\\':
ec = "\\\\";
break;
default:
break;
}

if (ec) {
rstrcat(&es, ec);
} else if (*c > 0 && *c < 0x20) {
char *uc = NULL;
rasprintf(&uc, "\\u%04x", *c);
rstrcat(&es, uc);
free(uc);
} else {
char t[2] = " ";
*t = *c;
rstrcat(&es, t);
}
}
rstrcat(&es, "\"");
return es;
}

static char *jsonFormat(rpmtd td, char **emsg)
{
int escape = 1;
char *val = NULL;

switch (rpmtdClass(td)) {
case RPM_BINARY_CLASS:
/* we don't want newlines in the binary output */
val = rpmBase64Encode(td->data, td->count, 0);
break;
case RPM_NUMERIC_CLASS:
escape = 0;
/* fallthrough */
default:
val = stringFormat(td, emsg);
break;
}

if (escape) {
char *s = jsonEscape(val);
free(val);
val = s;
}
return val;
}

/* signature fingerprint and time formatting */
static char * pgpsigFormat(rpmtd td, char **emsg)
{
Expand Down Expand Up @@ -545,6 +620,8 @@ static const struct headerFmt_s rpmHeaderFormats[] = {
RPM_ANY_CLASS, tagnameFormat },
{ RPMTD_FORMAT_TAGNUM, "tagnum",
RPM_ANY_CLASS, tagnumFormat },
{ RPMTD_FORMAT_JSON, "json",
RPM_ANY_CLASS, jsonFormat },
{ -1, NULL, 0, NULL }
};

Expand Down
62 changes: 62 additions & 0 deletions lib/headerfmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,66 @@ static struct xformat_s xformat_xml = {
.xTagFooter = xmlTagFooter,
};

static void jsonHeader(headerSprintfArgs hsa)
{
hsaAppend(hsa, "{\n");
}

static void jsonFooter(headerSprintfArgs hsa)
{
/*
* XXX HACK: when iterating through the header tags, we don't know
* which element will be the last one. We have to emit ',' for each
* tag we process and then delete the last one here.
*/
hsa->vallen -= 2;
hsaAppend(hsa, "\n}\n");
}

static void jsonTagHeader(headerSprintfArgs hsa, rpmTagVal tag, int nelem)
{
char *tagname = tagName(tag);
hsaAppend(hsa, " \"");
hsaAppend(hsa, tagname);
hsaAppend(hsa, "\": ");
if (nelem > 1)
hsaAppend(hsa, "[\n");
free(tagname);
}

static void jsonTagFooter(headerSprintfArgs hsa, rpmTagVal tag, int nelem)
{
if (nelem > 1)
hsaAppend(hsa, " ]");
hsaAppend(hsa, ",");
hsaAppend(hsa, "\n");
}

static void jsonItemHeader(headerSprintfArgs hsa, rpmTagVal tag,
int n, int nelem)
{
hsaAppend(hsa, "\t");
}

static void jsonItemFooter(headerSprintfArgs hsa, rpmTagVal tag,
int n, int nelem)
{
if (nelem > 1) {
if (n < nelem-1)
hsaAppend(hsa, ",");
hsaAppend(hsa, "\n");
}
}

static struct xformat_s xformat_json = {
.xHeader = jsonHeader,
.xFooter = jsonFooter,
.xTagHeader = jsonTagHeader,
.xTagFooter = jsonTagFooter,
.xItemHeader = jsonItemHeader,
.xItemFooter = jsonItemFooter,
};

/**
* Search tags for a name.
* @param hsa headerSprintf args
Expand Down Expand Up @@ -886,6 +946,8 @@ char * headerFormat(Header h, const char * fmt, errmsg_t * errmsg)
if (tag != NULL && tag->tag == -2 && tag->type != NULL) {
if (rstreq(tag->type, "xml"))
hsa.xfmt = xformat_xml; /* struct assignment */
else if (rstreq(tag->type, "json"))
hsa.xfmt = xformat_json; /* struct assignment */
}

if (hsa.xfmt.xHeader)
Expand Down
3 changes: 3 additions & 0 deletions rpmpopt.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ rpm alias --changes --qf '[* %{CHANGELOGTIME:date} %{CHANGELOGNAME}\n%{CHANGELOG
rpm alias --xml --qf '[%{*:xml}\n]' \
--POPTdesc=$"list metadata in xml"

rpm alias --json --qf '[%{*:json}]' \
--POPTdesc=$"list metadata in JSON"

rpm alias --triggerscripts --qf '\
[trigger%{TRIGGERTYPE} scriptlet (using %{TRIGGERSCRIPTPROG}) -- %{TRIGGERCONDS}\n\
%{TRIGGERSCRIPTS}\n]'
Expand Down
Loading

0 comments on commit 22d6281

Please sign in to comment.