yerba 0.9.1-aarch64-linux-gnu → 0.9.2-aarch64-linux-gnu
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 +27 -0
- data/exe/aarch64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +13 -0
- data/ext/yerba/yerba.c +28 -0
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/selector.rb +140 -0
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +1 -0
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/ffi.rs +58 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54884da10fcebdd93bae06b08f1b4e2c095cb78d845ad1fd40be0ed82773e61b
|
|
4
|
+
data.tar.gz: 31993433107b57dc155524b47761689276246787fe713a61a2b99f9517fe358e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cbcea74c06a512a6e7269cd10ef26522caabf57d1f34e759cd5a20e7f4b63d63512f9b65f3e634a099c48d63a8b31dd1c135502ce28459807f218f5e49cfd44d
|
|
7
|
+
data.tar.gz: 35ec7b2a3c6ddc8b35386fc8a957452b32aef12317a9eb7e38b6930a712866cab1275b8151a4bf26553899052704cef2e4df892d7787bd19fb611a0ae740330b
|
data/README.md
CHANGED
|
@@ -1165,6 +1165,33 @@ scalars.first.value = "New Title"
|
|
|
1165
1165
|
scalars.first.document.save!
|
|
1166
1166
|
```
|
|
1167
1167
|
|
|
1168
|
+
### Parsing Selectors
|
|
1169
|
+
|
|
1170
|
+
`Yerba::Selector` splits a selector into its segments, using the same parser as the rest of Yerba. Keys are Strings, indexes Integers, `[]` is `:all_items` and `*` is `:all_keys`:
|
|
1171
|
+
|
|
1172
|
+
```ruby
|
|
1173
|
+
selector = Yerba::Selector.parse("[3].talks[1]")
|
|
1174
|
+
|
|
1175
|
+
selector.segments # => [3, "talks", 1]
|
|
1176
|
+
selector.parent # => "[3].talks"
|
|
1177
|
+
selector.join("speakers", 0).to_s # => "[3].talks[1].speakers[0]"
|
|
1178
|
+
|
|
1179
|
+
Yerba::Selector.parse("[].speakers[]").segments # => [:all_items, "speakers", :all_items]
|
|
1180
|
+
Yerba::Selector.parse(".title").relative? # => true
|
|
1181
|
+
|
|
1182
|
+
Yerba::Selector.build([3, "talks", 1]).to_s # => "[3].talks[1]"
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
`dig` looks a concrete selector up in plain Ruby values, such as the cached `Document#values`, without calling into the native extension. Use it when reading many values from the same document:
|
|
1186
|
+
|
|
1187
|
+
```ruby
|
|
1188
|
+
values = document.values
|
|
1189
|
+
|
|
1190
|
+
Yerba::Selector.parse("[3].talks[1].title").dig(values) # => same as document.value_at("[3].talks[1].title")
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
Parsed selectors are cached per string, so parsing the same selector again is free.
|
|
1194
|
+
|
|
1168
1195
|
### Saving
|
|
1169
1196
|
|
|
1170
1197
|
Write changes back to the original file:
|
data/exe/aarch64-linux-gnu/yerba
CHANGED
|
Binary file
|
data/ext/yerba/include/yerba.h
CHANGED
|
@@ -93,6 +93,19 @@ char *yerba_document_resolve_selectors(const struct Document *document, const ch
|
|
|
93
93
|
|
|
94
94
|
bool yerba_selector_has_wildcard(const char *path);
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Parses a selector into JSON: `{"relative": bool, "segments": [...]}`.
|
|
98
|
+
*
|
|
99
|
+
* Keys are encoded as strings, indexes as integers, `[]` as `{"all": "items"}` and `*` as `{"all": "keys"}`.
|
|
100
|
+
*/
|
|
101
|
+
char *yerba_selector_parse(const char *path);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Builds a selector string from a JSON array of segments, encoded like `yerba_selector_parse` returns them.
|
|
105
|
+
*/
|
|
106
|
+
char *yerba_selector_build(const char *segments_json,
|
|
107
|
+
bool relative);
|
|
108
|
+
|
|
96
109
|
char *yerba_document_keys(const struct Document *document, const char *path);
|
|
97
110
|
|
|
98
111
|
char *yerba_document_get_quote_style(const struct Document *document, const char *path);
|
data/ext/yerba/yerba.c
CHANGED
|
@@ -1167,6 +1167,30 @@ static VALUE collection_s_find(int argc, VALUE *argv, VALUE self) {
|
|
|
1167
1167
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
/* Selector.native_parse(selector) → {"relative" => bool, "segments" => [...]} */
|
|
1171
|
+
static VALUE selector_s_native_parse(VALUE klass, VALUE selector) {
|
|
1172
|
+
char *json = yerba_selector_parse(StringValueCStr(selector));
|
|
1173
|
+
|
|
1174
|
+
if (!json) return Qnil;
|
|
1175
|
+
|
|
1176
|
+
VALUE json_string = make_utf8_string(json);
|
|
1177
|
+
yerba_string_free(json);
|
|
1178
|
+
|
|
1179
|
+
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/* Selector.native_build(segments_json, relative) → selector string */
|
|
1183
|
+
static VALUE selector_s_native_build(VALUE klass, VALUE segments_json, VALUE relative) {
|
|
1184
|
+
char *result = yerba_selector_build(StringValueCStr(segments_json), RTEST(relative));
|
|
1185
|
+
|
|
1186
|
+
if (!result) return rb_str_new_cstr("");
|
|
1187
|
+
|
|
1188
|
+
VALUE selector = make_utf8_string(result);
|
|
1189
|
+
yerba_string_free(result);
|
|
1190
|
+
|
|
1191
|
+
return selector;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1170
1194
|
/* Yerbafile.locate(directory = nil) → path string or nil */
|
|
1171
1195
|
static VALUE yerbafile_s_locate(int argc, VALUE *argv, VALUE klass) {
|
|
1172
1196
|
VALUE directory;
|
|
@@ -1193,6 +1217,10 @@ void Init_yerba(void) {
|
|
|
1193
1217
|
rb_eParseError = rb_define_class_under(rb_mYerba, "ParseError", rb_eError);
|
|
1194
1218
|
rb_ePathValidationError = rb_define_class_under(rb_mYerba, "PathValidationError", rb_eError);
|
|
1195
1219
|
|
|
1220
|
+
VALUE rb_cSelector = rb_define_class_under(rb_mYerba, "Selector", rb_cObject);
|
|
1221
|
+
rb_define_private_method(rb_singleton_class(rb_cSelector), "native_parse", selector_s_native_parse, 1);
|
|
1222
|
+
rb_define_private_method(rb_singleton_class(rb_cSelector), "native_build", selector_s_native_build, 2);
|
|
1223
|
+
|
|
1196
1224
|
VALUE rb_cCollection = rb_define_class_under(rb_mYerba, "Collection", rb_cObject);
|
|
1197
1225
|
rb_define_singleton_method(rb_cCollection, "get", collection_s_get, 2);
|
|
1198
1226
|
rb_define_singleton_method(rb_cCollection, "find", collection_s_find, -1);
|
data/lib/yerba/3.2/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/3.3/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/3.4/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/4.0/yerba.so
CHANGED
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Yerba
|
|
6
|
+
# A parsed selector, split into its segments.
|
|
7
|
+
#
|
|
8
|
+
# Yerba::Selector.parse("[3].talks[1]").segments # => [3, "talks", 1]
|
|
9
|
+
# Yerba::Selector.parse("[].speakers[]").segments # => [:all_items, "speakers", :all_items]
|
|
10
|
+
# Yerba::Selector.parse("database.*").segments # => ["database", :all_keys]
|
|
11
|
+
# Yerba::Selector.parse(".title").relative? # => true
|
|
12
|
+
#
|
|
13
|
+
# Keys are Strings, indexes Integers, `[]` is `:all_items` and `*` is `:all_keys`.
|
|
14
|
+
#
|
|
15
|
+
class Selector
|
|
16
|
+
ALL_KEYS = :all_keys
|
|
17
|
+
ALL_ITEMS = :all_items
|
|
18
|
+
|
|
19
|
+
attr_reader :segments
|
|
20
|
+
|
|
21
|
+
def self.parse(selector)
|
|
22
|
+
return selector if selector.is_a?(Selector)
|
|
23
|
+
|
|
24
|
+
string = selector.to_s
|
|
25
|
+
|
|
26
|
+
cache[string] ||= begin
|
|
27
|
+
parsed = native_parse(string)
|
|
28
|
+
|
|
29
|
+
new(parsed["segments"].map { |segment| decode(segment) }, relative: parsed["relative"], source: string)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Builds a selector from segments: Strings (keys), Integers (indexes), `:all_items` or `:all_keys`.
|
|
34
|
+
#
|
|
35
|
+
# Yerba::Selector.build([3, "talks", 1]).to_s # => "[3].talks[1]"
|
|
36
|
+
#
|
|
37
|
+
def self.build(segments, relative: false)
|
|
38
|
+
new(segments, relative: relative)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.clear_cache!
|
|
42
|
+
@cache = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.cache
|
|
46
|
+
@cache ||= {}
|
|
47
|
+
end
|
|
48
|
+
private_class_method :cache
|
|
49
|
+
|
|
50
|
+
def self.decode(segment)
|
|
51
|
+
case segment
|
|
52
|
+
when Hash
|
|
53
|
+
segment["all"] == "keys" ? ALL_KEYS : ALL_ITEMS
|
|
54
|
+
else
|
|
55
|
+
segment
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
private_class_method :decode
|
|
59
|
+
|
|
60
|
+
def initialize(segments, relative: false, source: nil)
|
|
61
|
+
@segments = segments.dup.freeze
|
|
62
|
+
@relative = relative
|
|
63
|
+
@source = source
|
|
64
|
+
|
|
65
|
+
freeze
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def relative?
|
|
69
|
+
@relative
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def absolute?
|
|
73
|
+
!@relative
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def empty?
|
|
77
|
+
segments.empty?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def wildcard?
|
|
81
|
+
segments.any? { |segment| [ALL_ITEMS, ALL_KEYS].include?(segment) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# "[3].talks[1]" => "[3].talks"
|
|
85
|
+
def parent
|
|
86
|
+
self.class.build(segments[0...-1], relative: relative?)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Selector.parse("[3]").join("talks", 1) => "[3].talks[1]"
|
|
90
|
+
def join(*more)
|
|
91
|
+
self.class.build(segments + more, relative: relative?)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def dig(values)
|
|
95
|
+
raise ArgumentError, "Cannot dig a wildcard selector (#{self}), use Document#find or Collection.find instead" if wildcard?
|
|
96
|
+
|
|
97
|
+
segments.reduce(values) do |current, segment|
|
|
98
|
+
case current
|
|
99
|
+
when Array
|
|
100
|
+
segment.is_a?(Integer) ? current[segment] : nil
|
|
101
|
+
when Hash
|
|
102
|
+
segment.is_a?(Integer) ? nil : current[segment]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_s
|
|
108
|
+
@source || self.class.send(:native_build, JSON.generate(encoded_segments), relative?)
|
|
109
|
+
end
|
|
110
|
+
alias to_str to_s
|
|
111
|
+
|
|
112
|
+
def ==(other)
|
|
113
|
+
other.is_a?(Selector) && other.segments == segments && other.relative? == relative?
|
|
114
|
+
end
|
|
115
|
+
alias eql? ==
|
|
116
|
+
|
|
117
|
+
def hash
|
|
118
|
+
[self.class, segments, relative?].hash
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def inspect
|
|
122
|
+
"#<#{self.class.name} #{to_s.inspect} #{segments.inspect}>"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def encoded_segments
|
|
128
|
+
segments.map do |segment|
|
|
129
|
+
case segment
|
|
130
|
+
when ALL_ITEMS
|
|
131
|
+
{ all: "items" }
|
|
132
|
+
when ALL_KEYS
|
|
133
|
+
{ all: "keys" }
|
|
134
|
+
else
|
|
135
|
+
segment
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/yerba/version.rb
CHANGED
data/lib/yerba.rb
CHANGED
data/rust/Cargo.lock
CHANGED
data/rust/Cargo.toml
CHANGED
data/rust/src/ffi.rs
CHANGED
|
@@ -342,6 +342,64 @@ pub unsafe extern "C" fn yerba_selector_has_wildcard(path: *const c_char) -> boo
|
|
|
342
342
|
crate::selector::Selector::parse(selector_string).has_wildcard()
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
/// Parses a selector into JSON: `{"relative": bool, "segments": [...]}`.
|
|
346
|
+
///
|
|
347
|
+
/// Keys are encoded as strings, indexes as integers, `[]` as `{"all": "items"}` and `*` as `{"all": "keys"}`.
|
|
348
|
+
#[no_mangle]
|
|
349
|
+
pub unsafe extern "C" fn yerba_selector_parse(path: *const c_char) -> *mut c_char {
|
|
350
|
+
use crate::selector::{Selector, SelectorSegment};
|
|
351
|
+
|
|
352
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
353
|
+
let selector = Selector::parse(selector_string);
|
|
354
|
+
|
|
355
|
+
let segments: Vec<serde_json::Value> = selector
|
|
356
|
+
.segments()
|
|
357
|
+
.iter()
|
|
358
|
+
.map(|segment| match segment {
|
|
359
|
+
SelectorSegment::Key(key) => serde_json::json!(key),
|
|
360
|
+
SelectorSegment::Index(index) => serde_json::json!(index),
|
|
361
|
+
SelectorSegment::AllItems => serde_json::json!({ "all": "items" }),
|
|
362
|
+
SelectorSegment::AllKeys => serde_json::json!({ "all": "keys" }),
|
|
363
|
+
})
|
|
364
|
+
.collect();
|
|
365
|
+
|
|
366
|
+
let json = serde_json::json!({ "relative": selector.is_relative(), "segments": segments });
|
|
367
|
+
|
|
368
|
+
CString::new(json.to_string()).unwrap_or_default().into_raw()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/// Builds a selector string from a JSON array of segments, encoded like `yerba_selector_parse` returns them.
|
|
372
|
+
#[no_mangle]
|
|
373
|
+
pub unsafe extern "C" fn yerba_selector_build(segments_json: *const c_char, relative: bool) -> *mut c_char {
|
|
374
|
+
use crate::selector::{Selector, SelectorSegment};
|
|
375
|
+
|
|
376
|
+
let json_string = CStr::from_ptr(segments_json).to_str().unwrap_or("[]");
|
|
377
|
+
let values: Vec<serde_json::Value> = serde_json::from_str(json_string).unwrap_or_default();
|
|
378
|
+
|
|
379
|
+
let segments: Vec<SelectorSegment> = values
|
|
380
|
+
.iter()
|
|
381
|
+
.filter_map(|value| match value {
|
|
382
|
+
serde_json::Value::String(key) => Some(SelectorSegment::Key(key.clone())),
|
|
383
|
+
serde_json::Value::Number(number) => number.as_u64().map(|index| SelectorSegment::Index(index as usize)),
|
|
384
|
+
serde_json::Value::Object(object) => match object.get("all").and_then(|all| all.as_str()) {
|
|
385
|
+
Some("items") => Some(SelectorSegment::AllItems),
|
|
386
|
+
Some("keys") => Some(SelectorSegment::AllKeys),
|
|
387
|
+
_ => None,
|
|
388
|
+
},
|
|
389
|
+
_ => None,
|
|
390
|
+
})
|
|
391
|
+
.collect();
|
|
392
|
+
|
|
393
|
+
let selector_string = Selector::Absolute(segments).to_selector_string();
|
|
394
|
+
let selector_string = if relative && !selector_string.is_empty() {
|
|
395
|
+
format!(".{}", selector_string)
|
|
396
|
+
} else {
|
|
397
|
+
selector_string
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
CString::new(selector_string).unwrap_or_default().into_raw()
|
|
401
|
+
}
|
|
402
|
+
|
|
345
403
|
#[no_mangle]
|
|
346
404
|
pub unsafe extern "C" fn yerba_document_keys(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
347
405
|
let document = &*document;
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yerba
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: aarch64-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A CLI tool for editing YAML while preserving structure, comments, and
|
|
14
14
|
format.
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- lib/yerba/node.rb
|
|
40
40
|
- lib/yerba/query_result.rb
|
|
41
41
|
- lib/yerba/scalar.rb
|
|
42
|
+
- lib/yerba/selector.rb
|
|
42
43
|
- lib/yerba/sequence.rb
|
|
43
44
|
- lib/yerba/version.rb
|
|
44
45
|
- lib/yerba/yerbafile.rb
|