Skip to content

Commit ae521bb

Browse files
committed
lib/json.c: add json printing stuff
1 parent 6baa761 commit ae521bb

7 files changed

Lines changed: 374 additions & 1 deletion

File tree

lib/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o
5151
OBJS += repo.o repo_sync.o
5252
OBJS += rpool.o cb_util.o proplib_wrapper.o
5353
OBJS += package_alternatives.o
54-
OBJS += conf.o log.o format.o
54+
OBJS += conf.o log.o format.o json.o
5555
OBJS += $(EXTOBJS) $(COMPAT_OBJS)
5656
# unnecessary unless pkgdb format changes
5757
# OBJS += pkgdb_conversion.o

lib/json.c

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck <mail@duncano.de> */
2+
/* SPDX-License-Identifier: BSD-2-Clause */
3+
4+
#include <sys/types.h>
5+
6+
#include <errno.h>
7+
#include <inttypes.h>
8+
#include <stdarg.h>
9+
#include <stdbool.h>
10+
#include <stdint.h>
11+
#include <stdio.h>
12+
13+
#include "xbps/xbps_array.h"
14+
#include "xbps/xbps_bool.h"
15+
#include "xbps/xbps_dictionary.h"
16+
#include "xbps/xbps_number.h"
17+
#include "xbps/xbps_object.h"
18+
#include "xbps/xbps_string.h"
19+
20+
#include "xbps/json.h"
21+
22+
int
23+
xbps_json_printf(struct xbps_json_printer *p, const char *fmt, ...)
24+
{
25+
va_list ap;
26+
int r = 0;
27+
va_start(ap, fmt);
28+
if (vfprintf(p->file, fmt, ap) < 0)
29+
r = errno ? -errno : -EIO;
30+
va_end(ap);
31+
return r;
32+
}
33+
34+
int
35+
xbps_json_print_escape(struct xbps_json_printer *p, const char *s)
36+
{
37+
int r = 0;
38+
for (; r >= 0 && *s; s++) {
39+
switch (*s) {
40+
case '"': r = xbps_json_printf(p, "\\\""); break;
41+
case '\\': r = xbps_json_printf(p, "\\\\"); break;
42+
case '\b': r = xbps_json_printf(p, "\\b"); break;
43+
case '\f': r = xbps_json_printf(p, "\\f"); break;
44+
case '\n': r = xbps_json_printf(p, "\\n"); break;
45+
case '\r': r = xbps_json_printf(p, "\\r"); break;
46+
case '\t': r = xbps_json_printf(p, "\\t"); break;
47+
default:
48+
if ((unsigned)*s < 0x20) {
49+
r = xbps_json_printf(p, "\\u%04x", *s);
50+
} else {
51+
r = xbps_json_printf(p, "%c", *s);
52+
}
53+
}
54+
}
55+
return r;
56+
}
57+
58+
int
59+
xbps_json_print_quote(struct xbps_json_printer *p, const char *s)
60+
{
61+
int r;
62+
if ((r = xbps_json_printf(p, "\"")) < 0)
63+
return r;
64+
if ((r = xbps_json_print_escape(p, s)) < 0)
65+
return r;
66+
return xbps_json_printf(p, "\"");
67+
}
68+
69+
int
70+
xbps_json_print_bool(struct xbps_json_printer *p, bool b)
71+
{
72+
return xbps_json_printf(p, b ? "true" : "false");
73+
}
74+
75+
int
76+
xbps_json_print_xbps_string(struct xbps_json_printer *p, xbps_string_t str)
77+
{
78+
return xbps_json_print_quote(p, xbps_string_cstring_nocopy(str));
79+
}
80+
81+
int
82+
xbps_json_print_xbps_number(struct xbps_json_printer *p, xbps_number_t num)
83+
{
84+
if (xbps_number_unsigned(num)) {
85+
return xbps_json_printf(p, "%" PRIu64, xbps_number_unsigned_integer_value(num));
86+
} else {
87+
return xbps_json_printf(p, "%" PRId64, xbps_number_integer_value(num));
88+
}
89+
return 0;
90+
}
91+
92+
int
93+
xbps_json_print_xbps_boolean(struct xbps_json_printer *p, xbps_bool_t b)
94+
{
95+
return xbps_json_print_bool(p, xbps_bool_true(b));
96+
}
97+
98+
int
99+
xbps_json_print_xbps_array(struct xbps_json_printer *p, xbps_array_t array)
100+
{
101+
const char *item_sep = p->compact ? "," : ", ";
102+
int indent = 0;
103+
unsigned i = 0;
104+
int r;
105+
p->depth++;
106+
if (!p->compact && p->indent > 0) {
107+
indent = p->indent*p->depth;
108+
item_sep = ",\n";
109+
}
110+
if ((r = xbps_json_printf(p, "[")) < 0)
111+
return r;
112+
for (; i < xbps_array_count(array); i++) {
113+
if (i == 0) {
114+
if (indent > 0 && (r = xbps_json_printf(p, "\n%*s", indent, "")) < 0)
115+
return r;
116+
} else if ((r = xbps_json_printf(p, "%s%*s", item_sep, indent, "")) < 0) {
117+
return r;
118+
}
119+
if ((r = xbps_json_print_xbps_object(p, xbps_array_get(array, i))) < 0)
120+
return r;
121+
}
122+
123+
p->depth--;
124+
if (indent > 0 && i > 0)
125+
return xbps_json_printf(p, "\n%*s]", p->indent*p->depth, "");
126+
return xbps_json_printf(p, "]");
127+
}
128+
129+
int
130+
xbps_json_print_xbps_dictionary(struct xbps_json_printer *p, xbps_dictionary_t dict)
131+
{
132+
xbps_object_iterator_t iter;
133+
xbps_object_t keysym;
134+
const char *item_sep = p->compact ? "," : ", ";
135+
const char *key_sep = p->compact ? ":": ": ";
136+
bool first = true;
137+
int indent = 0;
138+
int r;
139+
140+
iter = xbps_dictionary_iterator(dict);
141+
if (!iter)
142+
return errno ? -errno : -ENOMEM;
143+
144+
p->depth++;
145+
if (!p->compact && p->indent > 0) {
146+
indent = p->depth*p->indent;
147+
item_sep = ",\n";
148+
}
149+
150+
if ((r = xbps_json_printf(p, "{")) < 0)
151+
goto err;
152+
153+
while ((keysym = xbps_object_iterator_next(iter))) {
154+
xbps_object_t obj;
155+
const char *key;
156+
157+
if (first) {
158+
first = false;
159+
if (p->indent > 0 && (r = xbps_json_printf(p, "\n%*s", indent, "")) < 0) {
160+
goto err;
161+
}
162+
} else if ((r = xbps_json_printf(p, "%s%*s", item_sep, indent, "")) < 0) {
163+
goto err;
164+
}
165+
166+
key = xbps_dictionary_keysym_cstring_nocopy(keysym);
167+
if ((r = xbps_json_print_quote(p, key)) < 0)
168+
goto err;
169+
if ((r = xbps_json_printf(p, "%s", key_sep)) < 0)
170+
goto err;
171+
172+
obj = xbps_dictionary_get_keysym(dict, keysym);
173+
if ((r = xbps_json_print_xbps_object(p, obj)) < 0)
174+
goto err;
175+
}
176+
177+
xbps_object_iterator_release(iter);
178+
p->depth--;
179+
if (indent > 0 && !first)
180+
return xbps_json_printf(p, "\n%*s}", p->indent*p->depth, "");
181+
return xbps_json_printf(p, "}");
182+
183+
err:
184+
xbps_object_iterator_release(iter);
185+
return r;
186+
}
187+
188+
int
189+
xbps_json_print_xbps_object(struct xbps_json_printer *p, xbps_object_t obj)
190+
{
191+
if (!obj) return xbps_json_printf(p, "null");
192+
switch (xbps_object_type(obj)) {
193+
case XBPS_TYPE_ARRAY: return xbps_json_print_xbps_array(p, obj);
194+
case XBPS_TYPE_BOOL: return xbps_json_print_xbps_boolean(p, obj);
195+
case XBPS_TYPE_DATA: return xbps_json_printf(p, "true");
196+
case XBPS_TYPE_DICTIONARY: return xbps_json_print_xbps_dictionary(p, obj);
197+
case XBPS_TYPE_DICT_KEYSYM: return -EINVAL;
198+
case XBPS_TYPE_NUMBER: return xbps_json_print_xbps_number(p, obj);
199+
case XBPS_TYPE_STRING: return xbps_json_print_xbps_string(p, obj);
200+
case XBPS_TYPE_UNKNOWN: return -EINVAL;
201+
}
202+
return -EINVAL;
203+
}

tests/xbps/libxbps/Kyuafile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ include('find_pkg_orphans/Kyuafile')
1313
include('pkgdb/Kyuafile')
1414
include('shell/Kyuafile')
1515
include('fmt/Kyuafile')
16+
include('json/Kyuafile')

tests/xbps/libxbps/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ SUBDIRS += pkgdb
1313
SUBDIRS += config
1414
SUBDIRS += shell
1515
SUBDIRS += fmt
16+
SUBDIRS += json
1617

1718
include ../../../mk/subdir.mk

tests/xbps/libxbps/json/Kyuafile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
syntax("kyuafile", 1)
2+
3+
test_suite("libxbps")
4+
5+
atf_test_program{name="json_test"}

tests/xbps/libxbps/json/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
TOPDIR = ../../../..
2+
-include $(TOPDIR)/config.mk
3+
4+
TESTSSUBDIR = xbps/libxbps/json
5+
TEST = json_test
6+
EXTRA_FILES = Kyuafile
7+
8+
include $(TOPDIR)/mk/test.mk

tests/xbps/libxbps/json/main.c

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* SPDX-FileCopyrightText: Copyright 2023 Duncan Overbruck <mail@duncano.de> */
2+
/* SPDX-License-Identifier: BSD-2-Clause */
3+
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <limits.h>
8+
9+
#include <atf-c.h>
10+
11+
#include <xbps.h>
12+
#include <xbps/json.h>
13+
#include <xbps/xbps_dictionary.h>
14+
15+
ATF_TC(xbps_json_print_escape);
16+
17+
ATF_TC_HEAD(xbps_json_print_escape, tc)
18+
{
19+
atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_escape");
20+
}
21+
22+
ATF_TC_BODY(xbps_json_print_escape, tc)
23+
{
24+
char *buf = NULL;
25+
size_t bufsz = 0;
26+
struct xbps_json_printer p = {0};
27+
28+
ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz));
29+
30+
ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\"\\\b\f\n\r\t"));
31+
ATF_REQUIRE_EQ(0, fflush(p.file));
32+
ATF_CHECK_STREQ("\\\"\\\\\\b\\f\\n\\r\\t", buf);
33+
34+
memset(buf, '\0', bufsz);
35+
ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET));
36+
ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "09azAZ !$#%^()%"));
37+
ATF_REQUIRE_EQ(0, fflush(p.file));
38+
ATF_CHECK_STREQ("09azAZ !$#%^()%", buf);
39+
40+
memset(buf, '\0', bufsz);
41+
ATF_CHECK_EQ(0, fseek(p.file, 0, SEEK_SET));
42+
ATF_CHECK_EQ(0, xbps_json_print_escape(&p, "\x01\x1F"));
43+
ATF_REQUIRE_EQ(0, fflush(p.file));
44+
ATF_CHECK_STREQ("\\u0001\\u001f", buf);
45+
}
46+
47+
ATF_TC(xbps_json_print_xbps_dictionary);
48+
49+
ATF_TC_HEAD(xbps_json_print_xbps_dictionary, tc)
50+
{
51+
atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_escape");
52+
}
53+
54+
ATF_TC_BODY(xbps_json_print_xbps_dictionary, tc)
55+
{
56+
char *buf = NULL;
57+
size_t bufsz = 0;
58+
struct xbps_json_printer p = {0};
59+
xbps_dictionary_t dict;
60+
static const char *s = ""
61+
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
62+
"<plist version=\"1.0\">\n"
63+
"<dict>\n"
64+
"<key>array-empty</key>\n"
65+
"<array>\n"
66+
"</array>\n"
67+
"<key>array-numbers</key>\n"
68+
"<array>\n"
69+
" <integer>1</integer>\n"
70+
" <integer>2</integer>\n"
71+
" <integer>3</integer>\n"
72+
"</array>\n"
73+
"<key>dict-empty</key>\n"
74+
"<dict></dict>\n"
75+
"<key>num-signed</key>\n"
76+
"<integer>1</integer>\n"
77+
"<key>num-unsigned</key>\n"
78+
"<integer>0x1</integer>\n"
79+
"<key>string</key>\n"
80+
"<string>hello world</string>\n"
81+
"</dict>\n"
82+
"</plist>\n";
83+
84+
ATF_REQUIRE(dict = xbps_dictionary_internalize(s));
85+
ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz));
86+
87+
ATF_REQUIRE_EQ(0, xbps_json_print_xbps_dictionary(&p, dict));
88+
ATF_REQUIRE_EQ(0, fflush(p.file));
89+
ATF_CHECK_STREQ("{\"array-empty\": [], \"array-numbers\": [1, 2, 3], \"dict-empty\": {}, \"num-signed\": 1, \"num-unsigned\": 1, \"string\": \"hello world\"}", buf);
90+
}
91+
92+
ATF_TC(xbps_json_print_xbps_dictionary_indented);
93+
94+
ATF_TC_HEAD(xbps_json_print_xbps_dictionary_indented, tc)
95+
{
96+
atf_tc_set_md_var(tc, "descr", "Test xbps_json_print_xbps_dictionary: with indents");
97+
}
98+
99+
ATF_TC_BODY(xbps_json_print_xbps_dictionary_indented, tc)
100+
{
101+
char *buf = NULL;
102+
size_t bufsz = 0;
103+
struct xbps_json_printer p = {.indent = 2};
104+
xbps_dictionary_t dict;
105+
static const char *s = ""
106+
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
107+
"<plist version=\"1.0\">\n"
108+
"<dict>\n"
109+
"<key>array-empty</key>\n"
110+
"<array>\n"
111+
"</array>\n"
112+
"<key>array-numbers</key>\n"
113+
"<array>\n"
114+
" <integer>1</integer>\n"
115+
" <integer>2</integer>\n"
116+
" <integer>3</integer>\n"
117+
"</array>\n"
118+
"<key>dict-empty</key>\n"
119+
"<dict></dict>\n"
120+
"<key>num-signed</key>\n"
121+
"<integer>1</integer>\n"
122+
"<key>num-unsigned</key>\n"
123+
"<integer>0x1</integer>\n"
124+
"<key>string</key>\n"
125+
"<string>hello world</string>\n"
126+
"</dict>\n"
127+
"</plist>\n";
128+
129+
ATF_REQUIRE(dict = xbps_dictionary_internalize(s));
130+
ATF_REQUIRE(p.file = open_memstream(&buf, &bufsz));
131+
132+
ATF_REQUIRE_EQ(0, xbps_json_print_xbps_dictionary(&p, dict));
133+
ATF_REQUIRE_EQ(0, fflush(p.file));
134+
ATF_CHECK_STREQ(
135+
"{\n"
136+
" \"array-empty\": [],\n"
137+
" \"array-numbers\": [\n"
138+
" 1,\n"
139+
" 2,\n"
140+
" 3\n"
141+
" ],\n"
142+
" \"dict-empty\": {},\n"
143+
" \"num-signed\": 1,\n"
144+
" \"num-unsigned\": 1,\n"
145+
" \"string\": \"hello world\"\n"
146+
"}", buf);
147+
}
148+
149+
ATF_TP_ADD_TCS(tp)
150+
{
151+
ATF_TP_ADD_TC(tp, xbps_json_print_escape);
152+
ATF_TP_ADD_TC(tp, xbps_json_print_xbps_dictionary);
153+
ATF_TP_ADD_TC(tp, xbps_json_print_xbps_dictionary_indented);
154+
return atf_no_error();
155+
}

0 commit comments

Comments
 (0)