yerba 0.9.0 → 0.9.2
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/ext/yerba/include/yerba.h +15 -0
- data/ext/yerba/yerba.c +46 -0
- data/lib/yerba/selector.rb +140 -0
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +1 -0
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/blank_lines.rs +1 -0
- data/rust/src/document/mod.rs +13 -1
- data/rust/src/ffi.rs +63 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbff6da2b4fadf2758f9a9711d26bd3ac14c5b8c6ebc9d3630103550d3dfa22b
|
|
4
|
+
data.tar.gz: 46b35dc5a175e797edf19dbc24fe4961593e2ef4fc5c498c2bd5e5eeb9ca1f85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09afa4c8dc4f0f921496c3fd52b26b432fac7815ab0f3dfbfdd0a45e498dc821438eaa2ea0d69946ec284c7baccb173132907550ba591cc7c3001a9fd62f0ba3'
|
|
7
|
+
data.tar.gz: 1848eee956eb9c53c8a385b902a571052814c9ba997a0e133f7b2dedc168ddd19018864729f8e5811f08eea658feb1c605dbac2284a1c58e5b59d52af6f30205
|
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/ext/yerba/include/yerba.h
CHANGED
|
@@ -68,6 +68,8 @@ struct YerbaParseResult yerba_document_parse_file(const char *path);
|
|
|
68
68
|
|
|
69
69
|
struct YerbaParseResult yerba_document_parse(const char *content);
|
|
70
70
|
|
|
71
|
+
uint64_t yerba_document_generation(const struct Document *document);
|
|
72
|
+
|
|
71
73
|
void yerba_document_free(struct Document *document);
|
|
72
74
|
|
|
73
75
|
struct YerbaGetResult yerba_document_get(const struct Document *document, const char *path);
|
|
@@ -91,6 +93,19 @@ char *yerba_document_resolve_selectors(const struct Document *document, const ch
|
|
|
91
93
|
|
|
92
94
|
bool yerba_selector_has_wildcard(const char *path);
|
|
93
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
|
+
|
|
94
109
|
char *yerba_document_keys(const struct Document *document, const char *path);
|
|
95
110
|
|
|
96
111
|
char *yerba_document_get_quote_style(const struct Document *document, const char *path);
|
data/ext/yerba/yerba.c
CHANGED
|
@@ -339,6 +339,23 @@ static VALUE document_get_value(VALUE self, VALUE path) {
|
|
|
339
339
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
/* document.values → the whole document as Ruby objects, cached until the document changes */
|
|
343
|
+
static VALUE document_values(VALUE self) {
|
|
344
|
+
struct Document *document = get_document(self);
|
|
345
|
+
VALUE generation = ULL2NUM(yerba_document_generation(document));
|
|
346
|
+
|
|
347
|
+
if (rb_equal(rb_ivar_get(self, rb_intern("values_generation")), generation)) {
|
|
348
|
+
return rb_ivar_get(self, rb_intern("values"));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
VALUE values = document_get_value(self, make_utf8_string(""));
|
|
352
|
+
|
|
353
|
+
rb_ivar_set(self, rb_intern("values"), values);
|
|
354
|
+
rb_ivar_set(self, rb_intern("values_generation"), generation);
|
|
355
|
+
|
|
356
|
+
return values;
|
|
357
|
+
}
|
|
358
|
+
|
|
342
359
|
/* document.selectors → ["database", "database.host", "tags", "tags[]", ...] */
|
|
343
360
|
static VALUE document_selectors(VALUE self) {
|
|
344
361
|
struct Document *document = get_document(self);
|
|
@@ -1150,6 +1167,30 @@ static VALUE collection_s_find(int argc, VALUE *argv, VALUE self) {
|
|
|
1150
1167
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
1151
1168
|
}
|
|
1152
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
|
+
|
|
1153
1194
|
/* Yerbafile.locate(directory = nil) → path string or nil */
|
|
1154
1195
|
static VALUE yerbafile_s_locate(int argc, VALUE *argv, VALUE klass) {
|
|
1155
1196
|
VALUE directory;
|
|
@@ -1176,6 +1217,10 @@ void Init_yerba(void) {
|
|
|
1176
1217
|
rb_eParseError = rb_define_class_under(rb_mYerba, "ParseError", rb_eError);
|
|
1177
1218
|
rb_ePathValidationError = rb_define_class_under(rb_mYerba, "PathValidationError", rb_eError);
|
|
1178
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
|
+
|
|
1179
1224
|
VALUE rb_cCollection = rb_define_class_under(rb_mYerba, "Collection", rb_cObject);
|
|
1180
1225
|
rb_define_singleton_method(rb_cCollection, "get", collection_s_get, 2);
|
|
1181
1226
|
rb_define_singleton_method(rb_cCollection, "find", collection_s_find, -1);
|
|
@@ -1189,6 +1234,7 @@ void Init_yerba(void) {
|
|
|
1189
1234
|
rb_define_method(rb_cDocument, "[]", document_bracket, 1);
|
|
1190
1235
|
rb_define_method(rb_cDocument, "node_at", document_bracket, 1);
|
|
1191
1236
|
rb_define_method(rb_cDocument, "value_at", document_get_value, 1);
|
|
1237
|
+
rb_define_method(rb_cDocument, "values", document_values, 0);
|
|
1192
1238
|
rb_define_method(rb_cDocument, "get_all", document_get_all, 1);
|
|
1193
1239
|
rb_define_method(rb_cDocument, "location", document_location, -1);
|
|
1194
1240
|
rb_define_method(rb_cDocument, "locations", document_locations, 1);
|
|
@@ -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.toml
CHANGED
|
@@ -34,6 +34,7 @@ pub struct Args {
|
|
|
34
34
|
/// Comma-separated map keys to separate from what follows them
|
|
35
35
|
#[arg(long)]
|
|
36
36
|
after: Option<String>,
|
|
37
|
+
/// Leave entries alone when their value is null, an empty collection, or an empty string
|
|
37
38
|
#[arg(long)]
|
|
38
39
|
skip_empty: bool,
|
|
39
40
|
#[arg(long)]
|
data/rust/src/document/mod.rs
CHANGED
|
@@ -31,6 +31,7 @@ pub struct LocatedNode {
|
|
|
31
31
|
|
|
32
32
|
use std::fs;
|
|
33
33
|
use std::path::{Path, PathBuf};
|
|
34
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
34
35
|
|
|
35
36
|
use rowan::ast::AstNode;
|
|
36
37
|
use rowan::{TextRange, TextSize};
|
|
@@ -126,10 +127,13 @@ pub enum InsertPosition {
|
|
|
126
127
|
FromSortOrder(Vec<String>),
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
|
|
131
|
+
|
|
129
132
|
#[derive(Debug)]
|
|
130
133
|
pub struct Document {
|
|
131
134
|
root: SyntaxNode,
|
|
132
135
|
path: Option<PathBuf>,
|
|
136
|
+
generation: u64,
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
impl Document {
|
|
@@ -138,7 +142,15 @@ impl Document {
|
|
|
138
142
|
|
|
139
143
|
check_duplicate_keys(&tree)?;
|
|
140
144
|
|
|
141
|
-
Ok(Document {
|
|
145
|
+
Ok(Document {
|
|
146
|
+
root: tree,
|
|
147
|
+
path: None,
|
|
148
|
+
generation: NEXT_GENERATION.fetch_add(1, Ordering::Relaxed),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pub fn generation(&self) -> u64 {
|
|
153
|
+
self.generation
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
pub fn parse_file(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
|
data/rust/src/ffi.rs
CHANGED
|
@@ -217,6 +217,11 @@ const EMPTY_LOCATION: YerbaLocation = YerbaLocation {
|
|
|
217
217
|
end_column: 0,
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
+
#[no_mangle]
|
|
221
|
+
pub unsafe extern "C" fn yerba_document_generation(document: *const Document) -> u64 {
|
|
222
|
+
(*document).generation()
|
|
223
|
+
}
|
|
224
|
+
|
|
220
225
|
#[no_mangle]
|
|
221
226
|
pub unsafe extern "C" fn yerba_document_free(document: *mut Document) {
|
|
222
227
|
if !document.is_null() {
|
|
@@ -337,6 +342,64 @@ pub unsafe extern "C" fn yerba_selector_has_wildcard(path: *const c_char) -> boo
|
|
|
337
342
|
crate::selector::Selector::parse(selector_string).has_wildcard()
|
|
338
343
|
}
|
|
339
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
|
+
|
|
340
403
|
#[no_mangle]
|
|
341
404
|
pub unsafe extern "C" fn yerba_document_keys(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
342
405
|
let document = &*document;
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
@@ -34,6 +34,7 @@ files:
|
|
|
34
34
|
- lib/yerba/node.rb
|
|
35
35
|
- lib/yerba/query_result.rb
|
|
36
36
|
- lib/yerba/scalar.rb
|
|
37
|
+
- lib/yerba/selector.rb
|
|
37
38
|
- lib/yerba/sequence.rb
|
|
38
39
|
- lib/yerba/version.rb
|
|
39
40
|
- lib/yerba/yerbafile.rb
|