oj 3.13.5 → 3.13.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/oj/parser.c +76 -28
- data/ext/oj/usual.c +15 -15
- data/lib/oj/version.rb +1 -1
- data/test/bar.rb +9 -28
- data/test/test_parser_usual.rb +9 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c01eed4e3f068cda64af85aa6fe2aa15d7c559a3d1947385a8122112ae1b80b4
|
4
|
+
data.tar.gz: caba410a8bfee592344b8cfb281da48932cc2871ee3cc7fe71dadefacb55dc13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15dc55ed4572c0dcba2a3345aee97e5ab17f3106eef44c3d0a3c9e258915c1d95cae3bda8bbce6f21d3de08c22662a07ce7e39a52b77ff6c1f660cb576054e1f
|
7
|
+
data.tar.gz: 768977fae9b1c60baa679e32eb3d66d02206eb4357c2f6c29189b1d2dd27982a59f65648f8ee11424211f4e80a2a4ca11f19b285bee18db1d08ddbaba10fa1ca
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 3.13.6 - 2021-09-11
|
4
|
+
|
5
|
+
- Fixed unicode UTF 8 parsing in string values.
|
6
|
+
|
7
|
+
- Fixed hash key allocation issue.
|
8
|
+
|
9
|
+
- The `Oj::Parser.new()` function now allows optional arguments that
|
10
|
+
set the allowed options for the mode. As an example
|
11
|
+
`Oj::Parser.new(:usual, cache_keys: true)`.
|
12
|
+
|
3
13
|
## 3.13.5 - 2021-09-08
|
4
14
|
|
5
15
|
- Assure value strings of zero length are not always cached.
|
data/ext/oj/parser.c
CHANGED
@@ -610,6 +610,9 @@ static void parse(ojParser p, const byte *json) {
|
|
610
610
|
printf("*** parse - mode: %c %s\n", p->map[256], (const char *)json);
|
611
611
|
#endif
|
612
612
|
for (; '\0' != *b; b++) {
|
613
|
+
#if DEBUG
|
614
|
+
printf("*** parse - mode: %c %02x %s => %c\n", p->map[256], *b, b, p->map[*b]);
|
615
|
+
#endif
|
613
616
|
switch (p->map[*b]) {
|
614
617
|
case SKIP_NEWLINE:
|
615
618
|
p->line++;
|
@@ -887,13 +890,17 @@ static void parse(ojParser p, const byte *json) {
|
|
887
890
|
buf_append_string(&p->buf, (const char *)start, b - start);
|
888
891
|
}
|
889
892
|
if ('"' == *b) {
|
893
|
+
p->funcs[p->stack[p->depth]].add_str(p);
|
890
894
|
p->map = p->next_map;
|
891
895
|
break;
|
892
896
|
}
|
893
897
|
b--;
|
894
898
|
break;
|
895
899
|
case STR_SLASH: p->map = esc_map; break;
|
896
|
-
case STR_QUOTE:
|
900
|
+
case STR_QUOTE:
|
901
|
+
p->funcs[p->stack[p->depth]].add_str(p);
|
902
|
+
p->map = p->next_map;
|
903
|
+
break;
|
897
904
|
case ESC_U:
|
898
905
|
p->map = u_map;
|
899
906
|
p->ri = 0;
|
@@ -1135,13 +1142,42 @@ extern void oj_set_parser_saj(ojParser p);
|
|
1135
1142
|
extern void oj_set_parser_usual(ojParser p);
|
1136
1143
|
extern void oj_set_parser_debug(ojParser p);
|
1137
1144
|
|
1145
|
+
static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) {
|
1146
|
+
ojParser p = (ojParser)ptr;
|
1147
|
+
const char *key = NULL;
|
1148
|
+
char set_key[64];
|
1149
|
+
long klen;
|
1150
|
+
|
1151
|
+
switch (rb_type(rkey)) {
|
1152
|
+
case RUBY_T_SYMBOL:
|
1153
|
+
rkey = rb_sym2str(rkey);
|
1154
|
+
// fall through
|
1155
|
+
case RUBY_T_STRING:
|
1156
|
+
key = rb_string_value_ptr(&rkey);
|
1157
|
+
klen = RSTRING_LEN(rkey);
|
1158
|
+
break;
|
1159
|
+
default: rb_raise(rb_eArgError, "option keys must be a symbol or string");
|
1160
|
+
}
|
1161
|
+
if ((long)sizeof(set_key) - 1 <= klen) {
|
1162
|
+
return ST_CONTINUE;
|
1163
|
+
}
|
1164
|
+
memcpy(set_key, key, klen);
|
1165
|
+
set_key[klen] = '=';
|
1166
|
+
set_key[klen + 1] = '\0';
|
1167
|
+
p->option(p, set_key, value);
|
1168
|
+
|
1169
|
+
return ST_CONTINUE;
|
1170
|
+
}
|
1171
|
+
|
1138
1172
|
/* Document-method: new
|
1139
1173
|
* call-seq: new(mode=nil)
|
1140
1174
|
*
|
1141
1175
|
* Creates a new Parser with the specified mode. If no mode is provided
|
1142
|
-
* validation is assumed.
|
1176
|
+
* validation is assumed. Optional arguments can be provided that match the
|
1177
|
+
* mode. For example with the :usual mode the call might look like
|
1178
|
+
* Oj::Parser.new(:usual, cache_keys: true).
|
1143
1179
|
*/
|
1144
|
-
static VALUE parser_new(VALUE
|
1180
|
+
static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
1145
1181
|
ojParser p = ALLOC(struct _ojParser);
|
1146
1182
|
|
1147
1183
|
#if HAVE_RB_EXT_RACTOR_SAFE
|
@@ -1151,33 +1187,45 @@ static VALUE parser_new(VALUE self, VALUE mode) {
|
|
1151
1187
|
memset(p, 0, sizeof(struct _ojParser));
|
1152
1188
|
buf_init(&p->key);
|
1153
1189
|
buf_init(&p->buf);
|
1154
|
-
|
1155
1190
|
p->map = value_map;
|
1156
|
-
|
1157
|
-
|
1191
|
+
|
1192
|
+
if (argc < 1) {
|
1193
|
+
oj_set_parser_validator(p);
|
1158
1194
|
} else {
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
case RUBY_T_SYMBOL:
|
1163
|
-
mode = rb_sym2str(mode);
|
1164
|
-
// fall through
|
1165
|
-
case RUBY_T_STRING: ms = RSTRING_PTR(mode); break;
|
1166
|
-
default: rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
|
1167
|
-
}
|
1168
|
-
if (0 == strcmp("usual", ms) || 0 == strcmp("standard", ms) || 0 == strcmp("strict", ms) ||
|
1169
|
-
0 == strcmp("compat", ms)) {
|
1170
|
-
oj_set_parser_usual(p);
|
1171
|
-
} else if (0 == strcmp("object", ms)) {
|
1172
|
-
// TBD
|
1173
|
-
} else if (0 == strcmp("saj", ms)) {
|
1174
|
-
oj_set_parser_saj(p);
|
1175
|
-
} else if (0 == strcmp("validate", ms)) {
|
1195
|
+
VALUE mode = argv[0];
|
1196
|
+
|
1197
|
+
if (Qnil == mode) {
|
1176
1198
|
oj_set_parser_validator(p);
|
1177
|
-
} else if (0 == strcmp("debug", ms)) {
|
1178
|
-
oj_set_parser_debug(p);
|
1179
1199
|
} else {
|
1180
|
-
|
1200
|
+
const char *ms = NULL;
|
1201
|
+
|
1202
|
+
switch (rb_type(mode)) {
|
1203
|
+
case RUBY_T_SYMBOL:
|
1204
|
+
mode = rb_sym2str(mode);
|
1205
|
+
// fall through
|
1206
|
+
case RUBY_T_STRING: ms = RSTRING_PTR(mode); break;
|
1207
|
+
default: rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
|
1208
|
+
}
|
1209
|
+
if (0 == strcmp("usual", ms) || 0 == strcmp("standard", ms) || 0 == strcmp("strict", ms) ||
|
1210
|
+
0 == strcmp("compat", ms)) {
|
1211
|
+
oj_set_parser_usual(p);
|
1212
|
+
} else if (0 == strcmp("object", ms)) {
|
1213
|
+
// TBD
|
1214
|
+
} else if (0 == strcmp("saj", ms)) {
|
1215
|
+
oj_set_parser_saj(p);
|
1216
|
+
} else if (0 == strcmp("validate", ms)) {
|
1217
|
+
oj_set_parser_validator(p);
|
1218
|
+
} else if (0 == strcmp("debug", ms)) {
|
1219
|
+
oj_set_parser_debug(p);
|
1220
|
+
} else {
|
1221
|
+
rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
|
1222
|
+
}
|
1223
|
+
}
|
1224
|
+
if (1 < argc) {
|
1225
|
+
VALUE ropts = argv[1];
|
1226
|
+
|
1227
|
+
Check_Type(ropts, T_HASH);
|
1228
|
+
rb_hash_foreach(ropts, opt_cb, (VALUE)p);
|
1181
1229
|
}
|
1182
1230
|
}
|
1183
1231
|
return Data_Wrap_Struct(parser_class, parser_mark, parser_free, p);
|
@@ -1204,7 +1252,7 @@ static VALUE parser_new(VALUE self, VALUE mode) {
|
|
1204
1252
|
* - *:usual*
|
1205
1253
|
* - _cache_keys=_ sets the value of the _cache_keys_ flag.
|
1206
1254
|
* - _cache_keys_ returns the value of the _cache_keys_ flag.
|
1207
|
-
* - _cache_strings=_ sets the value of the _cache_strings_ to
|
1255
|
+
* - _cache_strings=_ sets the value of the _cache_strings_ to a positive integer less than 35. Strings shorter than
|
1208
1256
|
* that length are cached.
|
1209
1257
|
* - _cache_strings_ returns the value of the _cache_strings_ integer value.
|
1210
1258
|
* - _cache_expunge=_ sets the value of the _cache_expunge_ where 0 never expunges, 1 expunges slowly, 2 expunges
|
@@ -1469,7 +1517,7 @@ static VALUE parser_validate(VALUE self) {
|
|
1469
1517
|
*/
|
1470
1518
|
void oj_parser_init() {
|
1471
1519
|
parser_class = rb_define_class_under(Oj, "Parser", rb_cObject);
|
1472
|
-
rb_define_module_function(parser_class, "new", parser_new, 1);
|
1520
|
+
rb_define_module_function(parser_class, "new", parser_new, -1);
|
1473
1521
|
rb_define_method(parser_class, "parse", parser_parse, 1);
|
1474
1522
|
rb_define_method(parser_class, "load", parser_load, 1);
|
1475
1523
|
rb_define_method(parser_class, "file", parser_file, 1);
|
data/ext/oj/usual.c
CHANGED
@@ -39,7 +39,7 @@ typedef struct _col {
|
|
39
39
|
typedef union _key {
|
40
40
|
struct {
|
41
41
|
int16_t len;
|
42
|
-
char buf[
|
42
|
+
char buf[30];
|
43
43
|
};
|
44
44
|
struct {
|
45
45
|
int16_t xlen; // should be the same as len
|
@@ -209,21 +209,21 @@ static void push(ojParser p, VALUE v) {
|
|
209
209
|
static VALUE cache_key(ojParser p, Key kp) {
|
210
210
|
Delegate d = (Delegate)p->ctx;
|
211
211
|
|
212
|
-
if ((size_t)kp->len < sizeof(kp->buf)
|
212
|
+
if ((size_t)kp->len < sizeof(kp->buf)) {
|
213
213
|
return cache_intern(d->key_cache, kp->buf, kp->len);
|
214
214
|
}
|
215
215
|
return cache_intern(d->key_cache, kp->key, kp->len);
|
216
216
|
}
|
217
217
|
|
218
218
|
static VALUE str_key(ojParser p, Key kp) {
|
219
|
-
if ((size_t)kp->len < sizeof(kp->buf)
|
219
|
+
if ((size_t)kp->len < sizeof(kp->buf)) {
|
220
220
|
return rb_str_freeze(rb_utf8_str_new(kp->buf, kp->len));
|
221
221
|
}
|
222
222
|
return rb_str_freeze(rb_utf8_str_new(kp->key, kp->len));
|
223
223
|
}
|
224
224
|
|
225
225
|
static VALUE sym_key(ojParser p, Key kp) {
|
226
|
-
if ((size_t)kp->len < sizeof(kp->buf)
|
226
|
+
if ((size_t)kp->len < sizeof(kp->buf)) {
|
227
227
|
return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->buf, kp->len)));
|
228
228
|
}
|
229
229
|
return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->key, kp->len)));
|
@@ -232,7 +232,7 @@ static VALUE sym_key(ojParser p, Key kp) {
|
|
232
232
|
static ID get_attr_id(ojParser p, Key kp) {
|
233
233
|
Delegate d = (Delegate)p->ctx;
|
234
234
|
|
235
|
-
if ((size_t)kp->len < sizeof(kp->buf)
|
235
|
+
if ((size_t)kp->len < sizeof(kp->buf)) {
|
236
236
|
return (ID)cache_intern(d->attr_cache, kp->buf, kp->len);
|
237
237
|
}
|
238
238
|
return (ID)cache_intern(d->attr_cache, kp->key, kp->len);
|
@@ -253,7 +253,7 @@ static void push_key(ojParser p) {
|
|
253
253
|
d->kend = d->khead + cap;
|
254
254
|
}
|
255
255
|
d->ktail->len = klen;
|
256
|
-
if (klen
|
256
|
+
if (klen < sizeof(d->ktail->buf)) {
|
257
257
|
memcpy(d->ktail->buf, key, klen);
|
258
258
|
d->ktail->buf[klen] = '\0';
|
259
259
|
} else {
|
@@ -336,7 +336,7 @@ static void close_object(ojParser p) {
|
|
336
336
|
#if HAVE_RB_HASH_BULK_INSERT
|
337
337
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
338
338
|
*vp = d->get_key(p, kp);
|
339
|
-
if (sizeof(kp->buf)
|
339
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
340
340
|
xfree(kp->key);
|
341
341
|
}
|
342
342
|
}
|
@@ -344,7 +344,7 @@ static void close_object(ojParser p) {
|
|
344
344
|
#else
|
345
345
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
346
346
|
rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
|
347
|
-
if (sizeof(kp->buf)
|
347
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
348
348
|
xfree(kp->key);
|
349
349
|
}
|
350
350
|
}
|
@@ -368,7 +368,7 @@ static void close_object_class(ojParser p) {
|
|
368
368
|
|
369
369
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
370
370
|
rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1));
|
371
|
-
if (sizeof(kp->buf)
|
371
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
372
372
|
xfree(kp->key);
|
373
373
|
}
|
374
374
|
}
|
@@ -396,7 +396,7 @@ static void close_object_create(ojParser p) {
|
|
396
396
|
#if HAVE_RB_HASH_BULK_INSERT
|
397
397
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
398
398
|
*vp = d->get_key(p, kp);
|
399
|
-
if (sizeof(kp->buf)
|
399
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
400
400
|
xfree(kp->key);
|
401
401
|
}
|
402
402
|
}
|
@@ -404,7 +404,7 @@ static void close_object_create(ojParser p) {
|
|
404
404
|
#else
|
405
405
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
406
406
|
rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
|
407
|
-
if (sizeof(kp->buf)
|
407
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
408
408
|
xfree(kp->key);
|
409
409
|
}
|
410
410
|
}
|
@@ -413,7 +413,7 @@ static void close_object_create(ojParser p) {
|
|
413
413
|
obj = rb_class_new_instance(0, NULL, d->hash_class);
|
414
414
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
415
415
|
rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1));
|
416
|
-
if (sizeof(kp->buf)
|
416
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
417
417
|
xfree(kp->key);
|
418
418
|
}
|
419
419
|
}
|
@@ -428,7 +428,7 @@ static void close_object_create(ojParser p) {
|
|
428
428
|
#if HAVE_RB_HASH_BULK_INSERT
|
429
429
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
430
430
|
*vp = d->get_key(p, kp);
|
431
|
-
if (sizeof(kp->buf)
|
431
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
432
432
|
xfree(kp->key);
|
433
433
|
}
|
434
434
|
}
|
@@ -436,7 +436,7 @@ static void close_object_create(ojParser p) {
|
|
436
436
|
#else
|
437
437
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
438
438
|
rb_hash_aset(arg, d->get_key(p, kp), *(vp + 1));
|
439
|
-
if (sizeof(kp->buf)
|
439
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
440
440
|
xfree(kp->key);
|
441
441
|
}
|
442
442
|
}
|
@@ -446,7 +446,7 @@ static void close_object_create(ojParser p) {
|
|
446
446
|
obj = rb_class_new_instance(0, NULL, clas);
|
447
447
|
for (vp = head; kp < d->ktail; kp++, vp += 2) {
|
448
448
|
rb_ivar_set(obj, get_attr_id(p, kp), *(vp + 1));
|
449
|
-
if (sizeof(kp->buf)
|
449
|
+
if (sizeof(kp->buf) <= (size_t)kp->len) {
|
450
450
|
xfree(kp->key);
|
451
451
|
}
|
452
452
|
}
|
data/lib/oj/version.rb
CHANGED
data/test/bar.rb
CHANGED
@@ -1,35 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$: <<
|
4
|
-
|
5
|
-
|
6
|
-
$: << File.join($oj_dir, dir)
|
7
|
-
end
|
8
|
-
|
9
|
-
require 'active_support'
|
10
|
-
require "active_support/json"
|
11
|
-
|
12
|
-
$s = "\u2014 & \n \u{1F618}"
|
13
|
-
|
14
|
-
=begin
|
15
|
-
def check(label)
|
16
|
-
puts "\n--- #{label} --------------------"
|
17
|
-
|
18
|
-
ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
|
19
|
-
puts "with standard_json == true: t.to_json - #{$t.to_json}"
|
20
|
-
ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
|
21
|
-
puts "with standard_json == false: t.to_json - #{$t.to_json}"
|
22
|
-
end
|
23
|
-
|
24
|
-
check('Before Oj')
|
25
|
-
=end
|
3
|
+
$: << '.'
|
4
|
+
$: << File.join(File.dirname(__FILE__), "../lib")
|
5
|
+
$: << File.join(File.dirname(__FILE__), "../ext")
|
26
6
|
|
27
7
|
require 'oj'
|
28
8
|
|
29
|
-
|
30
|
-
puts "ActiveSupport.encode(s) - #{ActiveSupport::JSON.encode($s)}"
|
9
|
+
json = %|[{"x12345678901234567890": true}]|
|
31
10
|
|
32
|
-
Oj.
|
33
|
-
|
11
|
+
p = Oj::Parser.new(:usual)
|
12
|
+
p.cache_keys = false
|
13
|
+
p.symbol_keys = true
|
14
|
+
x = p.parse(json)
|
34
15
|
|
35
|
-
|
16
|
+
pp x
|
data/test/test_parser_usual.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# encoding:
|
2
|
+
# encoding: utf-8
|
3
3
|
|
4
4
|
$: << File.dirname(__FILE__)
|
5
5
|
|
@@ -73,9 +73,14 @@ class UsualTest < Minitest::Test
|
|
73
73
|
assert_equal({a: true, b: false}, doc)
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
76
|
+
def test_strings
|
77
77
|
p = Oj::Parser.new(:usual)
|
78
|
-
p.
|
78
|
+
doc = p.parse('{"ぴ": "", "ぴ ": "x", "c": "ぴーたー", "d": " ぴーたー "}')
|
79
|
+
assert_equal({'ぴ' => '', 'ぴ ' => 'x', 'c' => 'ぴーたー', 'd' => ' ぴーたー '}, doc)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_capacity
|
83
|
+
p = Oj::Parser.new(:usual, capacity: 1000)
|
79
84
|
assert_equal(4096, p.capacity)
|
80
85
|
p.capacity = 5000
|
81
86
|
assert_equal(5000, p.capacity)
|
@@ -181,8 +186,7 @@ class UsualTest < Minitest::Test
|
|
181
186
|
end
|
182
187
|
|
183
188
|
def test_missing_class
|
184
|
-
p = Oj::Parser.new(:usual)
|
185
|
-
p.create_id = '^'
|
189
|
+
p = Oj::Parser.new(:usual, create_id: '^')
|
186
190
|
json = '{"a":true,"^":"Auto","b":false}'
|
187
191
|
doc = p.parse(json)
|
188
192
|
assert_equal(Hash, doc.class)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oj
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.13.
|
4
|
+
version: 3.13.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Ohler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -79,6 +79,9 @@ extensions:
|
|
79
79
|
- ext/oj/extconf.rb
|
80
80
|
extra_rdoc_files:
|
81
81
|
- README.md
|
82
|
+
- LICENSE
|
83
|
+
- CHANGELOG.md
|
84
|
+
- RELEASE_NOTES.md
|
82
85
|
- pages/Advanced.md
|
83
86
|
- pages/Compatibility.md
|
84
87
|
- pages/Custom.md
|