yerba 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +241 -53
- data/ext/yerba/include/yerba.h +13 -1
- data/ext/yerba/yerba.c +239 -113
- data/lib/yerba/document.rb +54 -18
- data/lib/yerba/map.rb +55 -43
- data/lib/yerba/node.rb +58 -0
- data/lib/yerba/scalar.rb +20 -23
- data/lib/yerba/sequence.rb +88 -55
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +2 -0
- data/rust/Cargo.toml +2 -1
- data/rust/src/commands/delete.rs +1 -1
- data/rust/src/commands/get.rs +47 -12
- data/rust/src/commands/insert.rs +1 -1
- data/rust/src/commands/location.rs +56 -0
- data/rust/src/commands/mod.rs +33 -5
- data/rust/src/commands/remove.rs +1 -1
- data/rust/src/commands/rename.rs +1 -1
- data/rust/src/commands/schema.rs +84 -0
- data/rust/src/commands/set.rs +1 -1
- data/rust/src/commands/unique.rs +80 -0
- data/rust/src/document/condition.rs +17 -1
- data/rust/src/document/get.rs +254 -23
- data/rust/src/document/mod.rs +90 -12
- data/rust/src/document/schema.rs +73 -0
- data/rust/src/document/set.rs +1 -1
- data/rust/src/document/sort.rs +19 -13
- data/rust/src/document/style.rs +3 -3
- data/rust/src/document/unique.rs +86 -0
- data/rust/src/error.rs +78 -0
- data/rust/src/ffi.rs +89 -9
- data/rust/src/lib.rs +5 -10
- data/rust/src/main.rs +2 -0
- data/rust/src/schema.rs +93 -0
- data/rust/src/syntax.rs +91 -31
- data/rust/src/yerbafile.rs +107 -18
- metadata +8 -1
data/ext/yerba/yerba.c
CHANGED
|
@@ -146,53 +146,6 @@ static VALUE document_s_parse(VALUE klass, VALUE content) {
|
|
|
146
146
|
return instance;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/* document.get(path) */
|
|
150
|
-
static VALUE document_get(VALUE self, VALUE path) {
|
|
151
|
-
struct Document *document = get_document(self);
|
|
152
|
-
YerbaGetResult result = yerba_document_get(document, StringValueCStr(path));
|
|
153
|
-
|
|
154
|
-
if (result.error) {
|
|
155
|
-
VALUE message = make_utf8_string(result.error);
|
|
156
|
-
yerba_get_result_free(result);
|
|
157
|
-
|
|
158
|
-
rb_raise(rb_ePathValidationError, "%s", StringValueCStr(message));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!result.is_list) {
|
|
162
|
-
VALUE ruby_value = typed_value_to_ruby(result.single);
|
|
163
|
-
yerba_get_result_free(result);
|
|
164
|
-
|
|
165
|
-
return ruby_value;
|
|
166
|
-
} else {
|
|
167
|
-
VALUE json_string = make_utf8_string(result.list.json);
|
|
168
|
-
yerba_get_result_free(result);
|
|
169
|
-
|
|
170
|
-
VALUE items = rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
171
|
-
long length = RARRAY_LEN(items);
|
|
172
|
-
VALUE array = rb_ary_new_capa(length);
|
|
173
|
-
|
|
174
|
-
for (long i = 0; i < length; i++) {
|
|
175
|
-
VALUE item = rb_ary_entry(items, i);
|
|
176
|
-
VALUE text = rb_hash_aref(item, rb_str_new_cstr("text"));
|
|
177
|
-
int type_value = NUM2INT(rb_hash_aref(item, rb_str_new_cstr("type")));
|
|
178
|
-
|
|
179
|
-
if (NIL_P(text)) {
|
|
180
|
-
rb_ary_push(array, Qnil);
|
|
181
|
-
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
YerbaTypedValue typed_value;
|
|
186
|
-
typed_value.text = (char *)StringValueCStr(text);
|
|
187
|
-
typed_value.value_type = (YerbaValueType)type_value;
|
|
188
|
-
|
|
189
|
-
rb_ary_push(array, typed_value_to_ruby(typed_value));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return array;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
149
|
static VALUE location_to_ruby(YerbaLocation location) {
|
|
197
150
|
VALUE klass = rb_path2class("Yerba::Location");
|
|
198
151
|
|
|
@@ -206,11 +159,9 @@ static VALUE location_to_ruby(YerbaLocation location) {
|
|
|
206
159
|
);
|
|
207
160
|
}
|
|
208
161
|
|
|
209
|
-
/*
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
YerbaGetResult result = yerba_document_get(document, StringValueCStr(path));
|
|
213
|
-
|
|
162
|
+
/* Build a Yerba::Scalar, Map, Sequence, or nil from a YerbaGetResult.
|
|
163
|
+
Frees the result internally. */
|
|
164
|
+
static VALUE node_from_get_result(YerbaGetResult result, VALUE self, VALUE path) {
|
|
214
165
|
if (result.error) {
|
|
215
166
|
VALUE message = make_utf8_string(result.error);
|
|
216
167
|
yerba_get_result_free(result);
|
|
@@ -218,7 +169,6 @@ static VALUE document_bracket(VALUE self, VALUE path) {
|
|
|
218
169
|
rb_raise(rb_ePathValidationError, "%s", StringValueCStr(message));
|
|
219
170
|
}
|
|
220
171
|
|
|
221
|
-
VALUE instance;
|
|
222
172
|
VALUE location = location_to_ruby(result.location);
|
|
223
173
|
VALUE key = Qnil;
|
|
224
174
|
|
|
@@ -226,37 +176,31 @@ static VALUE document_bracket(VALUE self, VALUE path) {
|
|
|
226
176
|
VALUE key_location = location_to_ruby(result.key_location);
|
|
227
177
|
VALUE key_value = make_utf8_string(result.key_name);
|
|
228
178
|
|
|
229
|
-
|
|
179
|
+
VALUE key_kwargs = rb_hash_new();
|
|
180
|
+
rb_hash_aset(key_kwargs, ID2SYM(rb_intern("value")), key_value);
|
|
181
|
+
key = rb_funcallv_kw(rb_path2class("Yerba::Scalar"), rb_intern("from_document"), 5, (VALUE[]){ Qnil, Qnil, key_location, Qnil, key_kwargs }, RB_PASS_KEYWORDS);
|
|
230
182
|
}
|
|
231
183
|
|
|
232
184
|
switch (result.node_type) {
|
|
233
185
|
case NODE_TYPE_SCALAR: {
|
|
234
|
-
VALUE klass = rb_path2class("Yerba::Scalar");
|
|
235
186
|
VALUE value = typed_value_to_ruby(result.single);
|
|
236
187
|
yerba_get_result_free(result);
|
|
237
188
|
|
|
238
|
-
|
|
189
|
+
VALUE kwargs = rb_hash_new();
|
|
190
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("value")), value);
|
|
239
191
|
|
|
240
|
-
return
|
|
192
|
+
return rb_funcallv_kw(rb_path2class("Yerba::Scalar"), rb_intern("from_document"), 5, (VALUE[]){ self, path, location, key, kwargs }, RB_PASS_KEYWORDS);
|
|
241
193
|
}
|
|
242
194
|
|
|
243
|
-
case NODE_TYPE_MAP:
|
|
195
|
+
case NODE_TYPE_MAP:
|
|
244
196
|
yerba_get_result_free(result);
|
|
245
|
-
VALUE klass = rb_path2class("Yerba::Map");
|
|
246
|
-
|
|
247
|
-
instance = rb_funcall(klass, rb_intern("new"), 4, self, path, location, key);
|
|
248
197
|
|
|
249
|
-
return
|
|
250
|
-
}
|
|
198
|
+
return rb_funcall(rb_path2class("Yerba::Map"), rb_intern("from_document"), 4, self, path, location, key);
|
|
251
199
|
|
|
252
|
-
case NODE_TYPE_SEQUENCE:
|
|
200
|
+
case NODE_TYPE_SEQUENCE:
|
|
253
201
|
yerba_get_result_free(result);
|
|
254
|
-
VALUE klass = rb_path2class("Yerba::Sequence");
|
|
255
|
-
|
|
256
|
-
instance = rb_funcall(klass, rb_intern("new"), 4, self, path, location, key);
|
|
257
202
|
|
|
258
|
-
return
|
|
259
|
-
}
|
|
203
|
+
return rb_funcall(rb_path2class("Yerba::Sequence"), rb_intern("from_document"), 4, self, path, location, key);
|
|
260
204
|
|
|
261
205
|
default:
|
|
262
206
|
yerba_get_result_free(result);
|
|
@@ -265,6 +209,47 @@ static VALUE document_bracket(VALUE self, VALUE path) {
|
|
|
265
209
|
}
|
|
266
210
|
}
|
|
267
211
|
|
|
212
|
+
/* document[](path) → Yerba::Scalar, Yerba::Map, Yerba::Sequence, Array, or nil */
|
|
213
|
+
static VALUE document_bracket(VALUE self, VALUE path) {
|
|
214
|
+
struct Document *document = get_document(self);
|
|
215
|
+
char index_buffer[32];
|
|
216
|
+
|
|
217
|
+
if (RB_TYPE_P(path, T_FIXNUM)) {
|
|
218
|
+
snprintf(index_buffer, sizeof(index_buffer), "[%ld]", FIX2LONG(path));
|
|
219
|
+
path = rb_str_new_cstr(index_buffer);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const char *selector = StringValueCStr(path);
|
|
223
|
+
|
|
224
|
+
/* Wildcard: resolve to concrete selectors and return array of nodes */
|
|
225
|
+
if (strstr(selector, "[]") != NULL) {
|
|
226
|
+
char *json = yerba_document_resolve_selectors(document, selector);
|
|
227
|
+
|
|
228
|
+
if (!json) return rb_ary_new();
|
|
229
|
+
|
|
230
|
+
VALUE json_string = make_utf8_string(json);
|
|
231
|
+
yerba_string_free(json);
|
|
232
|
+
|
|
233
|
+
VALUE resolved = rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
234
|
+
long length = RARRAY_LEN(resolved);
|
|
235
|
+
VALUE array = rb_ary_new_capa(length);
|
|
236
|
+
|
|
237
|
+
for (long i = 0; i < length; i++) {
|
|
238
|
+
VALUE concrete_path = rb_ary_entry(resolved, i);
|
|
239
|
+
YerbaGetResult result = yerba_document_get(document, StringValueCStr(concrete_path));
|
|
240
|
+
VALUE node = node_from_get_result(result, self, concrete_path);
|
|
241
|
+
|
|
242
|
+
if (!NIL_P(node)) rb_ary_push(array, node);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return array;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
YerbaGetResult result = yerba_document_get(document, selector);
|
|
249
|
+
|
|
250
|
+
return node_from_get_result(result, self, path);
|
|
251
|
+
}
|
|
252
|
+
|
|
268
253
|
/* document.exists?(path) */
|
|
269
254
|
static VALUE document_exists_p(VALUE self, VALUE path) {
|
|
270
255
|
struct Document *document = get_document(self);
|
|
@@ -272,6 +257,13 @@ static VALUE document_exists_p(VALUE self, VALUE path) {
|
|
|
272
257
|
return yerba_document_exists(document, StringValueCStr(path)) ? Qtrue : Qfalse;
|
|
273
258
|
}
|
|
274
259
|
|
|
260
|
+
/* document.valid_selector?(selector) → true/false */
|
|
261
|
+
static VALUE document_valid_selector_p(VALUE self, VALUE path) {
|
|
262
|
+
struct Document *document = get_document(self);
|
|
263
|
+
|
|
264
|
+
return yerba_document_valid_selector(document, StringValueCStr(path)) ? Qtrue : Qfalse;
|
|
265
|
+
}
|
|
266
|
+
|
|
275
267
|
/* document.get_value(path) → parsed Ruby object (Hash/Array/String/Integer/etc) */
|
|
276
268
|
static VALUE document_get_value(VALUE self, VALUE path) {
|
|
277
269
|
struct Document *document = get_document(self);
|
|
@@ -285,10 +277,59 @@ static VALUE document_get_value(VALUE self, VALUE path) {
|
|
|
285
277
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
286
278
|
}
|
|
287
279
|
|
|
288
|
-
/* document.
|
|
289
|
-
static VALUE
|
|
280
|
+
/* document.selectors → ["database", "database.host", "tags", "tags[]", ...] */
|
|
281
|
+
static VALUE document_selectors(VALUE self) {
|
|
290
282
|
struct Document *document = get_document(self);
|
|
291
|
-
char *json =
|
|
283
|
+
char *json = yerba_document_selectors(document);
|
|
284
|
+
|
|
285
|
+
if (!json) return rb_ary_new();
|
|
286
|
+
|
|
287
|
+
VALUE json_string = make_utf8_string(json);
|
|
288
|
+
yerba_string_free(json);
|
|
289
|
+
|
|
290
|
+
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* document.locations(selector) → [Yerba::Location, ...] */
|
|
294
|
+
static VALUE document_locations(VALUE self, VALUE path) {
|
|
295
|
+
struct Document *document = get_document(self);
|
|
296
|
+
char *json = yerba_document_resolve_selectors(document, StringValueCStr(path));
|
|
297
|
+
|
|
298
|
+
if (!json) return rb_ary_new();
|
|
299
|
+
|
|
300
|
+
VALUE json_string = make_utf8_string(json);
|
|
301
|
+
yerba_string_free(json);
|
|
302
|
+
|
|
303
|
+
VALUE resolved = rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
304
|
+
long length = RARRAY_LEN(resolved);
|
|
305
|
+
VALUE array = rb_ary_new_capa(length);
|
|
306
|
+
|
|
307
|
+
for (long i = 0; i < length; i++) {
|
|
308
|
+
VALUE concrete_path = rb_ary_entry(resolved, i);
|
|
309
|
+
YerbaGetResult result = yerba_document_get(document, StringValueCStr(concrete_path));
|
|
310
|
+
|
|
311
|
+
if (result.error) {
|
|
312
|
+
yerba_get_result_free(result);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (result.location.start_line == 0 && result.location.end_line == 0) {
|
|
317
|
+
yerba_get_result_free(result);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
VALUE location = location_to_ruby(result.location);
|
|
322
|
+
yerba_get_result_free(result);
|
|
323
|
+
rb_ary_push(array, location);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return array;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* document.resolve_selectors(path) → ["[0].speakers[0]", "[0].speakers[1]", ...] */
|
|
330
|
+
static VALUE document_resolve_selectors(VALUE self, VALUE path) {
|
|
331
|
+
struct Document *document = get_document(self);
|
|
332
|
+
char *json = yerba_document_resolve_selectors(document, StringValueCStr(path));
|
|
292
333
|
|
|
293
334
|
if (!json) return rb_ary_new();
|
|
294
335
|
|
|
@@ -379,6 +420,41 @@ static VALUE document_find(int argc, VALUE *argv, VALUE self) {
|
|
|
379
420
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
380
421
|
}
|
|
381
422
|
|
|
423
|
+
/* Convert a Ruby VALUE to a C string + YerbaValueType pair.
|
|
424
|
+
The caller must provide a number_buffer of at least 64 bytes. */
|
|
425
|
+
struct TypedValue {
|
|
426
|
+
const char *text;
|
|
427
|
+
YerbaValueType type;
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
static struct TypedValue ruby_to_typed_value(VALUE value, char *number_buffer) {
|
|
431
|
+
struct TypedValue result;
|
|
432
|
+
|
|
433
|
+
if (value == Qnil) {
|
|
434
|
+
result.text = "null";
|
|
435
|
+
result.type = YERBA_VALUE_TYPE_NULL;
|
|
436
|
+
} else if (value == Qtrue) {
|
|
437
|
+
result.text = "true";
|
|
438
|
+
result.type = YERBA_VALUE_TYPE_BOOLEAN;
|
|
439
|
+
} else if (value == Qfalse) {
|
|
440
|
+
result.text = "false";
|
|
441
|
+
result.type = YERBA_VALUE_TYPE_BOOLEAN;
|
|
442
|
+
} else if (RB_INTEGER_TYPE_P(value)) {
|
|
443
|
+
snprintf(number_buffer, 64, "%ld", NUM2LONG(value));
|
|
444
|
+
result.text = number_buffer;
|
|
445
|
+
result.type = YERBA_VALUE_TYPE_INTEGER;
|
|
446
|
+
} else if (RB_FLOAT_TYPE_P(value)) {
|
|
447
|
+
snprintf(number_buffer, 64, "%g", NUM2DBL(value));
|
|
448
|
+
result.text = number_buffer;
|
|
449
|
+
result.type = YERBA_VALUE_TYPE_FLOAT;
|
|
450
|
+
} else {
|
|
451
|
+
result.text = StringValueCStr(value);
|
|
452
|
+
result.type = YERBA_VALUE_TYPE_STRING;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
|
|
382
458
|
/* document.set(path, value, condition: nil, if_exists: false, if_missing: false) */
|
|
383
459
|
static VALUE document_set(int argc, VALUE *argv, VALUE self) {
|
|
384
460
|
VALUE path, value, opts;
|
|
@@ -396,40 +472,17 @@ static VALUE document_set(int argc, VALUE *argv, VALUE self) {
|
|
|
396
472
|
if (RTEST(v_if_missing) && yerba_document_exists(document, StringValueCStr(path))) return self;
|
|
397
473
|
}
|
|
398
474
|
|
|
399
|
-
const char *c_value;
|
|
400
|
-
YerbaValueType value_type;
|
|
401
475
|
char number_buffer[64];
|
|
476
|
+
struct TypedValue typed_value = ruby_to_typed_value(value, number_buffer);
|
|
402
477
|
bool all = false;
|
|
403
478
|
|
|
404
|
-
if (value == Qnil) {
|
|
405
|
-
c_value = "null";
|
|
406
|
-
value_type = YERBA_VALUE_TYPE_NULL;
|
|
407
|
-
} else if (value == Qtrue) {
|
|
408
|
-
c_value = "true";
|
|
409
|
-
value_type = YERBA_VALUE_TYPE_BOOLEAN;
|
|
410
|
-
} else if (value == Qfalse) {
|
|
411
|
-
c_value = "false";
|
|
412
|
-
value_type = YERBA_VALUE_TYPE_BOOLEAN;
|
|
413
|
-
} else if (RB_INTEGER_TYPE_P(value)) {
|
|
414
|
-
snprintf(number_buffer, sizeof(number_buffer), "%ld", NUM2LONG(value));
|
|
415
|
-
c_value = number_buffer;
|
|
416
|
-
value_type = YERBA_VALUE_TYPE_INTEGER;
|
|
417
|
-
} else if (RB_FLOAT_TYPE_P(value)) {
|
|
418
|
-
snprintf(number_buffer, sizeof(number_buffer), "%g", NUM2DBL(value));
|
|
419
|
-
c_value = number_buffer;
|
|
420
|
-
value_type = YERBA_VALUE_TYPE_FLOAT;
|
|
421
|
-
} else {
|
|
422
|
-
c_value = StringValueCStr(value);
|
|
423
|
-
value_type = YERBA_VALUE_TYPE_STRING;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
479
|
if (!NIL_P(opts)) {
|
|
427
480
|
VALUE v_all = rb_hash_aref(opts, ID2SYM(rb_intern("all")));
|
|
428
481
|
|
|
429
482
|
if (RTEST(v_all)) all = true;
|
|
430
483
|
}
|
|
431
484
|
|
|
432
|
-
YerbaResult result = yerba_document_set(document, StringValueCStr(path),
|
|
485
|
+
YerbaResult result = yerba_document_set(document, StringValueCStr(path), typed_value.text, typed_value.type, all);
|
|
433
486
|
check_result(result);
|
|
434
487
|
|
|
435
488
|
return self;
|
|
@@ -454,8 +507,11 @@ static VALUE document_insert(int argc, VALUE *argv, VALUE self) {
|
|
|
454
507
|
if (!NIL_P(v_at)) at = NUM2LL(v_at);
|
|
455
508
|
}
|
|
456
509
|
|
|
510
|
+
char number_buffer[64];
|
|
511
|
+
struct TypedValue typed_value = ruby_to_typed_value(value, number_buffer);
|
|
512
|
+
|
|
457
513
|
struct Document *document = get_document(self);
|
|
458
|
-
YerbaResult result = yerba_document_insert(document, StringValueCStr(path),
|
|
514
|
+
YerbaResult result = yerba_document_insert(document, StringValueCStr(path), typed_value.text, typed_value.type, before, after, at);
|
|
459
515
|
check_result(result);
|
|
460
516
|
|
|
461
517
|
return self;
|
|
@@ -699,6 +755,24 @@ static VALUE document_apply_yerbafile(int argc, VALUE *argv, VALUE self) {
|
|
|
699
755
|
return self;
|
|
700
756
|
}
|
|
701
757
|
|
|
758
|
+
/* document.validate_schema(schema_json, selector = nil) */
|
|
759
|
+
static VALUE document_validate_schema(int argc, VALUE *argv, VALUE self) {
|
|
760
|
+
VALUE schema_json, selector;
|
|
761
|
+
rb_scan_args(argc, argv, "11", &schema_json, &selector);
|
|
762
|
+
|
|
763
|
+
struct Document *document = get_document(self);
|
|
764
|
+
const char *selector_str = NIL_P(selector) ? NULL : StringValueCStr(selector);
|
|
765
|
+
|
|
766
|
+
char *result = yerba_document_validate_schema(document, StringValueCStr(schema_json), selector_str);
|
|
767
|
+
|
|
768
|
+
if (!result) return rb_ary_new();
|
|
769
|
+
|
|
770
|
+
VALUE json_string = make_utf8_string(result);
|
|
771
|
+
yerba_string_free(result);
|
|
772
|
+
|
|
773
|
+
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
774
|
+
}
|
|
775
|
+
|
|
702
776
|
/* document.to_s */
|
|
703
777
|
static VALUE document_to_s(VALUE self) {
|
|
704
778
|
struct Document *document = get_document(self);
|
|
@@ -736,14 +810,41 @@ static VALUE document_changed_p(VALUE self) {
|
|
|
736
810
|
return rb_str_equal(current, original) ? Qfalse : Qtrue;
|
|
737
811
|
}
|
|
738
812
|
|
|
813
|
+
/* document.location(selector = nil) → Yerba::Location */
|
|
814
|
+
static VALUE document_location(int argc, VALUE *argv, VALUE self) {
|
|
815
|
+
VALUE path;
|
|
816
|
+
rb_scan_args(argc, argv, "01", &path);
|
|
817
|
+
|
|
818
|
+
struct Document *document = get_document(self);
|
|
819
|
+
const char *selector = NIL_P(path) ? "" : StringValueCStr(path);
|
|
820
|
+
|
|
821
|
+
YerbaGetResult result = yerba_document_get(document, selector);
|
|
822
|
+
|
|
823
|
+
if (result.error) {
|
|
824
|
+
yerba_get_result_free(result);
|
|
825
|
+
return Qnil;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (result.location.start_line == 0 && result.location.end_line == 0 && strlen(selector) > 0) {
|
|
829
|
+
yerba_get_result_free(result);
|
|
830
|
+
return Qnil;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
VALUE location = location_to_ruby(result.location);
|
|
834
|
+
yerba_get_result_free(result);
|
|
835
|
+
|
|
836
|
+
return location;
|
|
837
|
+
}
|
|
838
|
+
|
|
739
839
|
/* document.path */
|
|
740
840
|
static VALUE document_path(VALUE self) {
|
|
741
841
|
return rb_iv_get(self, "@path");
|
|
742
842
|
}
|
|
743
843
|
|
|
744
|
-
/* Collection.get(glob,
|
|
844
|
+
/* Collection.get(glob, selector) → [Yerba::Scalar|Map|Sequence, ...] */
|
|
745
845
|
static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
746
|
-
(void)self;
|
|
846
|
+
(void) self;
|
|
847
|
+
|
|
747
848
|
YerbaTypedList result = yerba_glob_get(StringValueCStr(pattern), StringValueCStr(path));
|
|
748
849
|
|
|
749
850
|
if (!result.json) return rb_ary_new();
|
|
@@ -755,20 +856,40 @@ static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
|
755
856
|
long length = RARRAY_LEN(items);
|
|
756
857
|
VALUE array = rb_ary_new_capa(length);
|
|
757
858
|
|
|
859
|
+
|
|
758
860
|
for (long i = 0; i < length; i++) {
|
|
759
861
|
VALUE item = rb_ary_entry(items, i);
|
|
760
|
-
VALUE
|
|
761
|
-
|
|
862
|
+
VALUE node_type = rb_hash_aref(item, rb_str_new_cstr("node_type"));
|
|
863
|
+
VALUE file_path = rb_hash_aref(item, rb_str_new_cstr("file_path"));
|
|
864
|
+
VALUE selector = rb_hash_aref(item, rb_str_new_cstr("selector"));
|
|
865
|
+
const char *type_str = NIL_P(node_type) ? "" : StringValueCStr(node_type);
|
|
762
866
|
|
|
763
|
-
if (NIL_P(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
867
|
+
if (NIL_P(selector)) continue;
|
|
868
|
+
|
|
869
|
+
VALUE line = rb_hash_aref(item, rb_str_new_cstr("line"));
|
|
870
|
+
VALUE kwargs = rb_hash_new();
|
|
767
871
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
872
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("selector")), selector);
|
|
873
|
+
if (!NIL_P(file_path)) rb_hash_aset(kwargs, ID2SYM(rb_intern("file_path")), file_path);
|
|
874
|
+
if (!NIL_P(line)) rb_hash_aset(kwargs, ID2SYM(rb_intern("line")), line);
|
|
875
|
+
|
|
876
|
+
if (strcmp(type_str, "scalar") == 0) {
|
|
877
|
+
VALUE text = rb_hash_aref(item, rb_str_new_cstr("text"));
|
|
878
|
+
if (NIL_P(text)) continue;
|
|
879
|
+
|
|
880
|
+
int type_value = NUM2INT(rb_hash_aref(item, rb_str_new_cstr("type")));
|
|
881
|
+
YerbaTypedValue typed_value;
|
|
882
|
+
typed_value.text = (char *)StringValueCStr(text);
|
|
883
|
+
typed_value.value_type = (YerbaValueType)type_value;
|
|
884
|
+
|
|
885
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("value")), typed_value_to_ruby(typed_value));
|
|
886
|
+
VALUE scalar_args[1] = { kwargs };
|
|
887
|
+
rb_ary_push(array, rb_funcallv_kw(rb_path2class("Yerba::Scalar"), rb_intern("from"), 1, scalar_args, RB_PASS_KEYWORDS));
|
|
888
|
+
} else {
|
|
889
|
+
const char *klass_name = strcmp(type_str, "sequence") == 0 ? "Yerba::Sequence" : "Yerba::Map";
|
|
890
|
+
VALUE node_args[1] = { kwargs };
|
|
891
|
+
rb_ary_push(array, rb_funcallv_kw(rb_path2class(klass_name), rb_intern("from"), 1, node_args, RB_PASS_KEYWORDS));
|
|
892
|
+
}
|
|
772
893
|
}
|
|
773
894
|
|
|
774
895
|
return array;
|
|
@@ -835,13 +956,17 @@ void Init_yerba(void) {
|
|
|
835
956
|
rb_define_alloc_func(rb_cDocument, document_alloc);
|
|
836
957
|
rb_define_method(rb_cDocument, "initialize", document_initialize, 1);
|
|
837
958
|
rb_define_singleton_method(rb_cDocument, "parse", document_s_parse, 1);
|
|
838
|
-
rb_define_method(rb_cDocument, "get", document_get, 1);
|
|
839
959
|
rb_define_method(rb_cDocument, "[]", document_bracket, 1);
|
|
840
|
-
rb_define_method(rb_cDocument, "
|
|
841
|
-
rb_define_method(rb_cDocument, "
|
|
960
|
+
rb_define_method(rb_cDocument, "node_at", document_bracket, 1);
|
|
961
|
+
rb_define_method(rb_cDocument, "value_at", document_get_value, 1);
|
|
962
|
+
rb_define_method(rb_cDocument, "location", document_location, -1);
|
|
963
|
+
rb_define_method(rb_cDocument, "locations", document_locations, 1);
|
|
964
|
+
rb_define_method(rb_cDocument, "selectors", document_selectors, 0);
|
|
965
|
+
rb_define_method(rb_cDocument, "resolve_selectors", document_resolve_selectors, 1);
|
|
842
966
|
rb_define_method(rb_cDocument, "get_quote_style", document_get_quote_style, 1);
|
|
843
967
|
rb_define_method(rb_cDocument, "set_quote_style", document_set_quote_style, 2);
|
|
844
968
|
rb_define_method(rb_cDocument, "exists?", document_exists_p, 1);
|
|
969
|
+
rb_define_method(rb_cDocument, "valid_selector?", document_valid_selector_p, 1);
|
|
845
970
|
rb_define_method(rb_cDocument, "condition?", document_condition_p, -1);
|
|
846
971
|
rb_define_method(rb_cDocument, "find", document_find, -1);
|
|
847
972
|
rb_define_method(rb_cDocument, "set", document_set, -1);
|
|
@@ -857,6 +982,7 @@ void Init_yerba(void) {
|
|
|
857
982
|
rb_define_method(rb_cDocument, "quote_style", document_quote_style, -1);
|
|
858
983
|
rb_define_method(rb_cDocument, "blank_lines", document_blank_lines, 2);
|
|
859
984
|
rb_define_method(rb_cDocument, "apply_yerbafile", document_apply_yerbafile, -1);
|
|
985
|
+
rb_define_method(rb_cDocument, "validate_schema", document_validate_schema, -1);
|
|
860
986
|
rb_define_method(rb_cDocument, "to_s", document_to_s, 0);
|
|
861
987
|
rb_define_method(rb_cDocument, "write!", document_save, 0);
|
|
862
988
|
rb_define_method(rb_cDocument, "changed?", document_changed_p, 0);
|
data/lib/yerba/document.rb
CHANGED
|
@@ -4,10 +4,26 @@ module Yerba
|
|
|
4
4
|
class Document
|
|
5
5
|
ROOT_SELECTOR = ""
|
|
6
6
|
|
|
7
|
+
def self.cache
|
|
8
|
+
@cache ||= Hash.new { |hash, path| hash[path] = new(path) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.clear_cache!
|
|
12
|
+
@cache = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def selector
|
|
16
|
+
ROOT_SELECTOR
|
|
17
|
+
end
|
|
18
|
+
|
|
7
19
|
def root
|
|
8
20
|
self[ROOT_SELECTOR]
|
|
9
21
|
end
|
|
10
22
|
|
|
23
|
+
def []=(key, value)
|
|
24
|
+
root[key] = value
|
|
25
|
+
end
|
|
26
|
+
|
|
11
27
|
def map?
|
|
12
28
|
root.is_a?(Map)
|
|
13
29
|
end
|
|
@@ -17,11 +33,11 @@ module Yerba
|
|
|
17
33
|
end
|
|
18
34
|
|
|
19
35
|
def to_h
|
|
20
|
-
|
|
36
|
+
value_at(ROOT_SELECTOR)
|
|
21
37
|
end
|
|
22
38
|
|
|
23
39
|
def to_a
|
|
24
|
-
|
|
40
|
+
value_at(ROOT_SELECTOR)
|
|
25
41
|
end
|
|
26
42
|
|
|
27
43
|
def to_yaml
|
|
@@ -29,25 +45,13 @@ module Yerba
|
|
|
29
45
|
end
|
|
30
46
|
|
|
31
47
|
def dig(*keys)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
result&.value
|
|
48
|
+
keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
|
|
35
49
|
end
|
|
36
50
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
values = get(path)
|
|
40
|
-
return [] unless values.is_a?(Array)
|
|
51
|
+
def fetch(selector)
|
|
52
|
+
validate_selector!(selector)
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
values.each_with_index.map do |_value, index|
|
|
45
|
-
resolved_path = path.sub("[]", "[#{index}]")
|
|
46
|
-
self[resolved_path]
|
|
47
|
-
end
|
|
48
|
-
else
|
|
49
|
-
self[path]
|
|
50
|
-
end
|
|
54
|
+
self[selector]
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def find_by(...)
|
|
@@ -90,6 +94,38 @@ module Yerba
|
|
|
90
94
|
self
|
|
91
95
|
end
|
|
92
96
|
|
|
97
|
+
def valid?(schema, selector: nil)
|
|
98
|
+
errors = validate(schema, selector: selector)
|
|
99
|
+
|
|
100
|
+
errors.empty?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def validate(schema, selector: nil)
|
|
104
|
+
schema_json = schema.is_a?(String) ? schema : JSON.generate(schema)
|
|
105
|
+
|
|
106
|
+
validate_schema(schema_json, selector)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_selector!(selector)
|
|
110
|
+
return if valid_selector?(selector)
|
|
111
|
+
|
|
112
|
+
available = selectors
|
|
113
|
+
message = "selector \"#{selector}\" is not valid for this document"
|
|
114
|
+
|
|
115
|
+
if available.any?
|
|
116
|
+
suggestions = DidYouMean::SpellChecker.new(dictionary: available).correct(selector)
|
|
117
|
+
|
|
118
|
+
if suggestions.any?
|
|
119
|
+
message += ". Did you mean: #{suggestions.first(3).join(", ")}?"
|
|
120
|
+
else
|
|
121
|
+
message += ". Available selectors: #{available.first(5).join(", ")}"
|
|
122
|
+
message += ", ..." if available.length > 5
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
raise Yerba::SelectorNotFoundError, message
|
|
127
|
+
end
|
|
128
|
+
|
|
93
129
|
def inspect
|
|
94
130
|
if path
|
|
95
131
|
"#<Yerba::Document path=#{path.inspect}>"
|