yerba 0.7.6-x86_64-linux-gnu → 0.8.0-x86_64-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 +66 -2
- data/exe/x86_64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +8 -0
- data/ext/yerba/yerba.c +70 -8
- 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/map.rb +15 -10
- data/lib/yerba/node.rb +2 -2
- data/lib/yerba/sequence.rb +13 -1
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/get.rs +12 -2
- data/rust/src/commands/rename.rs +3 -3
- data/rust/src/commands/set.rs +11 -1
- data/rust/src/document/condition.rs +89 -32
- data/rust/src/document/delete.rs +6 -1
- data/rust/src/document/get.rs +94 -26
- data/rust/src/document/insert.rs +22 -0
- data/rust/src/document/mod.rs +67 -18
- data/rust/src/document/set.rs +81 -0
- data/rust/src/error.rs +14 -0
- data/rust/src/ffi.rs +71 -9
- data/rust/src/json.rs +10 -1
- data/rust/src/lib.rs +14 -6
- data/rust/src/main.rs +1 -0
- data/rust/src/selector.rs +19 -3
- data/rust/src/syntax.rs +46 -25
- data/rust/src/yerbafile.rs +30 -6
- metadata +2 -2
data/rust/src/document/delete.rs
CHANGED
|
@@ -31,6 +31,7 @@ impl Document {
|
|
|
31
31
|
|
|
32
32
|
pub fn delete(&mut self, dot_path: &str) -> Result<(), YerbaError> {
|
|
33
33
|
Self::validate_path(dot_path)?;
|
|
34
|
+
self.refuse_flow_target(dot_path)?;
|
|
34
35
|
|
|
35
36
|
let selector = crate::selector::Selector::parse(dot_path);
|
|
36
37
|
let segments = selector.segments();
|
|
@@ -88,13 +89,17 @@ impl Document {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
crate::selector::SelectorSegment::Index(index) => self.remove_at(&parent_path, *index),
|
|
91
|
-
crate::selector::SelectorSegment::AllItems => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
|
|
92
|
+
crate::selector::SelectorSegment::AllItems | crate::selector::SelectorSegment::AllKeys => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
pub fn remove(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
96
97
|
let current_node = self.navigate(dot_path)?;
|
|
97
98
|
|
|
99
|
+
if find_flow_sequence(¤t_node).is_some() {
|
|
100
|
+
return Err(YerbaError::FlowCollectionNotWritable(dot_path.to_string()));
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
let sequence = find_block_sequence(¤t_node).ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
99
104
|
|
|
100
105
|
let target_entry = sequence
|
data/rust/src/document/get.rs
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
+
fn key_of(node: &SyntaxNode, source: &str) -> (Option<String>, Location) {
|
|
4
|
+
node
|
|
5
|
+
.parent()
|
|
6
|
+
.and_then(|parent| {
|
|
7
|
+
use yaml_parser::ast::{BlockMapEntry, FlowMapEntry};
|
|
8
|
+
|
|
9
|
+
let key_node = match BlockMapEntry::cast(parent.clone()) {
|
|
10
|
+
Some(entry) => entry.key().map(|key| key.syntax().clone()),
|
|
11
|
+
None => FlowMapEntry::cast(parent).and_then(|entry| entry.key().map(|key| key.syntax().clone())),
|
|
12
|
+
}?;
|
|
13
|
+
|
|
14
|
+
let key_text = extract_scalar_text(&key_node)?;
|
|
15
|
+
let key_range = key_node.text_range();
|
|
16
|
+
let key_location = compute_location(source, key_range.start().into(), key_range.end().into());
|
|
17
|
+
|
|
18
|
+
Some((key_text, key_location))
|
|
19
|
+
})
|
|
20
|
+
.map(|(name, location)| (Some(name), location))
|
|
21
|
+
.unwrap_or((None, Location::default()))
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
impl Document {
|
|
4
25
|
pub fn is_valid_selector(&self, dot_path: &str) -> bool {
|
|
5
26
|
if dot_path.is_empty() {
|
|
@@ -12,6 +33,10 @@ impl Document {
|
|
|
12
33
|
return true;
|
|
13
34
|
}
|
|
14
35
|
|
|
36
|
+
if crate::selector::Selector::parse(dot_path).has_wildcard() && !self.navigate_all_compact(dot_path).is_empty() {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
let wildcard_version = dot_path.replace(|c: char| c.is_ascii_digit(), "").replace("[.", "[");
|
|
16
41
|
|
|
17
42
|
let mut normalized = String::new();
|
|
@@ -82,6 +107,49 @@ impl Document {
|
|
|
82
107
|
self.navigate_all_compact(dot_path).iter().map(node_selector).collect()
|
|
83
108
|
}
|
|
84
109
|
|
|
110
|
+
pub fn keys_at(&self, dot_path: &str) -> Vec<String> {
|
|
111
|
+
let node = if dot_path.is_empty() {
|
|
112
|
+
self.root.clone()
|
|
113
|
+
} else {
|
|
114
|
+
match self.navigate_all_compact(dot_path).into_iter().next() {
|
|
115
|
+
Some(node) => node,
|
|
116
|
+
None => return Vec::new(),
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let collection = node.descendants().find(|descendant| {
|
|
121
|
+
matches!(
|
|
122
|
+
descendant.kind(),
|
|
123
|
+
SyntaxKind::BLOCK_MAP | SyntaxKind::BLOCK_SEQ | SyntaxKind::FLOW_MAP | SyntaxKind::FLOW_SEQ
|
|
124
|
+
)
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let Some(collection) = collection else {
|
|
128
|
+
return Vec::new();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if let Some(map) = BlockMap::cast(collection.clone()) {
|
|
132
|
+
return map
|
|
133
|
+
.entries()
|
|
134
|
+
.filter_map(|entry| entry.key().and_then(|key| extract_scalar_text(key.syntax())))
|
|
135
|
+
.collect();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if let Some(map) = yaml_parser::ast::FlowMap::cast(collection) {
|
|
139
|
+
return map
|
|
140
|
+
.entries()
|
|
141
|
+
.map(|entries| {
|
|
142
|
+
entries
|
|
143
|
+
.entries()
|
|
144
|
+
.filter_map(|entry| entry.key().and_then(|key| extract_scalar_text(key.syntax())))
|
|
145
|
+
.collect()
|
|
146
|
+
})
|
|
147
|
+
.unwrap_or_default();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Vec::new()
|
|
151
|
+
}
|
|
152
|
+
|
|
85
153
|
pub fn get_all_located(&self, dot_path: &str) -> Vec<LocatedNode> {
|
|
86
154
|
let source = self.source_text();
|
|
87
155
|
let file_path = self.path.as_ref().map(|p| p.to_string_lossy().to_string());
|
|
@@ -116,6 +184,8 @@ impl Document {
|
|
|
116
184
|
}
|
|
117
185
|
};
|
|
118
186
|
|
|
187
|
+
let (key_name, key_location) = key_of(node, &source);
|
|
188
|
+
|
|
119
189
|
LocatedNode {
|
|
120
190
|
node_type: node_type.to_string(),
|
|
121
191
|
text,
|
|
@@ -124,6 +194,8 @@ impl Document {
|
|
|
124
194
|
selector,
|
|
125
195
|
line,
|
|
126
196
|
location,
|
|
197
|
+
key_name,
|
|
198
|
+
key_location,
|
|
127
199
|
}
|
|
128
200
|
})
|
|
129
201
|
.collect()
|
|
@@ -214,23 +286,7 @@ impl Document {
|
|
|
214
286
|
let range = node.text_range();
|
|
215
287
|
let location = compute_location(source, range.start().into(), range.end().into());
|
|
216
288
|
|
|
217
|
-
let (key_name, key_location) = node
|
|
218
|
-
.parent()
|
|
219
|
-
.and_then(|parent| {
|
|
220
|
-
use yaml_parser::ast::BlockMapEntry;
|
|
221
|
-
|
|
222
|
-
BlockMapEntry::cast(parent).and_then(|entry| {
|
|
223
|
-
entry.key().and_then(|key_node| {
|
|
224
|
-
let key_text = extract_scalar_text(key_node.syntax())?;
|
|
225
|
-
let key_range = key_node.syntax().text_range();
|
|
226
|
-
let key_location = compute_location(source, key_range.start().into(), key_range.end().into());
|
|
227
|
-
|
|
228
|
-
Some((key_text, key_location))
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
.map(|(name, location)| (Some(name), location))
|
|
233
|
-
.unwrap_or((None, Location::default()));
|
|
289
|
+
let (key_name, key_location) = key_of(&node, source);
|
|
234
290
|
|
|
235
291
|
(location, key_name, key_location)
|
|
236
292
|
}
|
|
@@ -238,15 +294,15 @@ impl Document {
|
|
|
238
294
|
}
|
|
239
295
|
}
|
|
240
296
|
|
|
241
|
-
pub fn find_items(&self, dot_path: &str, condition: Option<&str>, select: Option<&str>) -> Vec<serde_json::Value> {
|
|
297
|
+
pub fn find_items(&self, dot_path: &str, condition: Option<&str>, select: Option<&str>) -> Result<Vec<serde_json::Value>, YerbaError> {
|
|
242
298
|
let values = match condition {
|
|
243
|
-
Some(cond) => self.filter(dot_path, cond)
|
|
299
|
+
Some(cond) => self.filter(dot_path, cond)?,
|
|
244
300
|
None => self.get_values(dot_path),
|
|
245
301
|
};
|
|
246
302
|
|
|
247
303
|
let select_fields: Option<Vec<&str>> = select.map(|s| s.split(',').collect());
|
|
248
304
|
|
|
249
|
-
values
|
|
305
|
+
let items = values
|
|
250
306
|
.iter()
|
|
251
307
|
.map(|value| match &select_fields {
|
|
252
308
|
Some(fields) => {
|
|
@@ -262,7 +318,9 @@ impl Document {
|
|
|
262
318
|
}
|
|
263
319
|
None => crate::json::yaml_to_json(value),
|
|
264
320
|
})
|
|
265
|
-
.collect()
|
|
321
|
+
.collect();
|
|
322
|
+
|
|
323
|
+
Ok(items)
|
|
266
324
|
}
|
|
267
325
|
|
|
268
326
|
pub fn get_value(&self, dot_path: &str) -> Option<yaml_serde::Value> {
|
|
@@ -337,7 +395,7 @@ impl Document {
|
|
|
337
395
|
}
|
|
338
396
|
|
|
339
397
|
pub fn exists(&self, dot_path: &str) -> bool {
|
|
340
|
-
if dot_path.
|
|
398
|
+
if crate::selector::Selector::parse(dot_path).has_wildcard() {
|
|
341
399
|
if !self.navigate_all_compact(dot_path).is_empty() {
|
|
342
400
|
return true;
|
|
343
401
|
}
|
|
@@ -455,11 +513,13 @@ pub(crate) fn node_selector(node: &SyntaxNode) -> String {
|
|
|
455
513
|
let mut parts: Vec<String> = Vec::new();
|
|
456
514
|
let mut current = node.clone();
|
|
457
515
|
|
|
458
|
-
if current.kind()
|
|
516
|
+
if matches!(current.kind(), SyntaxKind::BLOCK_SEQ_ENTRY | SyntaxKind::FLOW_SEQ_ENTRY) {
|
|
459
517
|
if let Some(parent) = current.parent() {
|
|
518
|
+
let kind = current.kind();
|
|
519
|
+
|
|
460
520
|
let index = parent
|
|
461
521
|
.children()
|
|
462
|
-
.filter(|child| child.kind() ==
|
|
522
|
+
.filter(|child| child.kind() == kind)
|
|
463
523
|
.position(|child| child == current)
|
|
464
524
|
.unwrap_or(0);
|
|
465
525
|
|
|
@@ -474,11 +534,11 @@ pub(crate) fn node_selector(node: &SyntaxNode) -> String {
|
|
|
474
534
|
};
|
|
475
535
|
|
|
476
536
|
match parent.kind() {
|
|
477
|
-
SyntaxKind::BLOCK_SEQ_ENTRY => {
|
|
537
|
+
SyntaxKind::BLOCK_SEQ_ENTRY | SyntaxKind::FLOW_SEQ_ENTRY => {
|
|
478
538
|
if let Some(grandparent) = parent.parent() {
|
|
479
539
|
let index = grandparent
|
|
480
540
|
.children()
|
|
481
|
-
.filter(|child| child.kind() ==
|
|
541
|
+
.filter(|child| child.kind() == parent.kind())
|
|
482
542
|
.position(|child| child == parent)
|
|
483
543
|
.unwrap_or(0);
|
|
484
544
|
|
|
@@ -494,6 +554,14 @@ pub(crate) fn node_selector(node: &SyntaxNode) -> String {
|
|
|
494
554
|
}
|
|
495
555
|
}
|
|
496
556
|
|
|
557
|
+
SyntaxKind::FLOW_MAP_ENTRY => {
|
|
558
|
+
if let Some(key_node) = parent.children().find(|child| child.kind() == SyntaxKind::FLOW_MAP_KEY) {
|
|
559
|
+
if let Some(key_text) = extract_scalar_text(&key_node) {
|
|
560
|
+
parts.push(key_text);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
497
565
|
_ => {}
|
|
498
566
|
}
|
|
499
567
|
|
data/rust/src/document/insert.rs
CHANGED
|
@@ -90,6 +90,28 @@ impl Document {
|
|
|
90
90
|
|
|
91
91
|
pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
92
92
|
Self::validate_path(dot_path)?;
|
|
93
|
+
self.refuse_flow_target(dot_path)?;
|
|
94
|
+
|
|
95
|
+
if let Some((parent_path, _)) = dot_path.rsplit_once('.') {
|
|
96
|
+
if let Ok(parent) = self.navigate(parent_path) {
|
|
97
|
+
let occupied_flow = find_flow_map(&parent).map(|map| !flow_map_entries(&map).is_empty()).unwrap_or(false)
|
|
98
|
+
|| find_flow_sequence(&parent)
|
|
99
|
+
.map(|sequence| !flow_sequence_entries(&sequence).is_empty())
|
|
100
|
+
.unwrap_or(false);
|
|
101
|
+
|
|
102
|
+
if occupied_flow {
|
|
103
|
+
return Err(YerbaError::FlowCollectionNotWritable(dot_path.to_string()));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if crate::selector::Selector::parse(dot_path)
|
|
109
|
+
.segments()
|
|
110
|
+
.iter()
|
|
111
|
+
.any(|segment| matches!(segment, crate::selector::SelectorSegment::AllKeys))
|
|
112
|
+
{
|
|
113
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
114
|
+
}
|
|
93
115
|
|
|
94
116
|
if let Ok(current_node) = self.navigate(dot_path) {
|
|
95
117
|
if find_block_sequence(¤t_node).is_some()
|
data/rust/src/document/mod.rs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
mod condition;
|
|
2
|
+
pub use condition::{validate_condition, validate_item_condition};
|
|
2
3
|
mod delete;
|
|
3
4
|
mod get;
|
|
4
5
|
mod insert;
|
|
@@ -20,6 +21,8 @@ pub struct LocatedNode {
|
|
|
20
21
|
pub selector: String,
|
|
21
22
|
pub line: usize,
|
|
22
23
|
pub location: Location,
|
|
24
|
+
pub key_name: Option<String>,
|
|
25
|
+
pub key_location: Location,
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
use std::fs;
|
|
@@ -35,9 +38,10 @@ use crate::error::YerbaError;
|
|
|
35
38
|
use crate::QuoteStyle;
|
|
36
39
|
|
|
37
40
|
use crate::syntax::{
|
|
38
|
-
column_at, dedent_block_scalar, extract_scalar, extract_scalar_text, find_block_map, find_block_sequence, find_entry_by_key,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
column_at, dedent_block_scalar, extract_scalar, extract_scalar_text, find_block_map, find_block_sequence, find_entry_by_key, find_flow_entry_by_key,
|
|
42
|
+
find_flow_map, find_flow_sequence, find_scalar_token, first_collection, flow_map_entries, flow_sequence_entries, format_scalar_value, in_flow_collection,
|
|
43
|
+
is_map_key, is_yaml_non_string, line_at, line_start_at, preceding_whitespace_indent, preceding_whitespace_token, raw_scalar_value, removal_range,
|
|
44
|
+
FirstCollection, ScalarValue,
|
|
41
45
|
};
|
|
42
46
|
|
|
43
47
|
#[derive(Debug, Clone)]
|
|
@@ -190,6 +194,14 @@ impl Document {
|
|
|
190
194
|
self.navigate_all(dot_path).into_iter().flatten().collect()
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
pub(crate) fn refuse_flow_target(&self, dot_path: &str) -> Result<(), YerbaError> {
|
|
198
|
+
if self.navigate_all_compact(dot_path).iter().any(in_flow_collection) {
|
|
199
|
+
return Err(YerbaError::FlowCollectionNotWritable(dot_path.to_string()));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Ok(())
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
pub fn navigate_all(&self, dot_path: &str) -> Vec<Option<SyntaxNode>> {
|
|
194
206
|
if Document::validate_path(dot_path).is_err() {
|
|
195
207
|
return Vec::new();
|
|
@@ -220,7 +232,7 @@ impl Document {
|
|
|
220
232
|
let segments = parsed.segments();
|
|
221
233
|
|
|
222
234
|
for (i, segment) in segments.iter().enumerate() {
|
|
223
|
-
let is_wildcard = matches!(segment, crate::selector::SelectorSegment::AllItems);
|
|
235
|
+
let is_wildcard = matches!(segment, crate::selector::SelectorSegment::AllItems | crate::selector::SelectorSegment::AllKeys);
|
|
224
236
|
let has_remaining = i + 1 < segments.len();
|
|
225
237
|
let mut next_nodes: Vec<Option<SyntaxNode>> = Vec::new();
|
|
226
238
|
|
|
@@ -335,22 +347,28 @@ impl Document {
|
|
|
335
347
|
return self.remove_node(entry_node);
|
|
336
348
|
}
|
|
337
349
|
|
|
338
|
-
let
|
|
339
|
-
next_sibling.text_range().start()
|
|
340
|
-
} else {
|
|
341
|
-
entry_node.parent().map(|parent| parent.text_range().end()).unwrap_or(entry_range.end())
|
|
342
|
-
};
|
|
350
|
+
let source = self.source_text();
|
|
343
351
|
|
|
344
|
-
let start
|
|
345
|
-
|
|
346
|
-
|
|
352
|
+
let (start, end) = match preceding_whitespace_token(entry_node) {
|
|
353
|
+
Some(whitespace_token) => {
|
|
354
|
+
let whitespace_text = whitespace_token.text();
|
|
355
|
+
let whitespace_start = whitespace_token.text_range().start();
|
|
347
356
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
let start = whitespace_text
|
|
358
|
+
.rfind('\n')
|
|
359
|
+
.map(|offset| whitespace_start + TextSize::from(offset as u32))
|
|
360
|
+
.unwrap_or(whitespace_start);
|
|
361
|
+
|
|
362
|
+
(start, entry_range.end())
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
None => {
|
|
366
|
+
let entry_end: usize = entry_range.end().into();
|
|
367
|
+
let line_start = line_start_at(&source, entry_range.start().into());
|
|
368
|
+
let after_entry = source[entry_end..].find('\n').map(|offset| entry_end + offset + 1).unwrap_or(entry_end);
|
|
369
|
+
|
|
370
|
+
(TextSize::from(line_start as u32), TextSize::from(after_entry as u32))
|
|
371
|
+
}
|
|
354
372
|
};
|
|
355
373
|
|
|
356
374
|
self.apply_edit(TextRange::new(start, end), "")
|
|
@@ -673,6 +691,8 @@ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment
|
|
|
673
691
|
SelectorSegment::AllItems => {
|
|
674
692
|
if let Some(sequence) = find_block_sequence(node) {
|
|
675
693
|
sequence.entries().map(|entry| entry.syntax().clone()).collect()
|
|
694
|
+
} else if let Some(sequence) = find_flow_sequence(node) {
|
|
695
|
+
flow_sequence_entries(&sequence)
|
|
676
696
|
} else {
|
|
677
697
|
Vec::new()
|
|
678
698
|
}
|
|
@@ -681,6 +701,25 @@ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment
|
|
|
681
701
|
SelectorSegment::Index(index) => {
|
|
682
702
|
if let Some(sequence) = find_block_sequence(node) {
|
|
683
703
|
sequence.entries().nth(*index).map(|entry| vec![entry.syntax().clone()]).unwrap_or_default()
|
|
704
|
+
} else if let Some(sequence) = find_flow_sequence(node) {
|
|
705
|
+
flow_sequence_entries(&sequence)
|
|
706
|
+
.into_iter()
|
|
707
|
+
.nth(*index)
|
|
708
|
+
.map(|entry| vec![entry])
|
|
709
|
+
.unwrap_or_default()
|
|
710
|
+
} else {
|
|
711
|
+
Vec::new()
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
SelectorSegment::AllKeys => {
|
|
716
|
+
if let Some(map) = find_block_map(node) {
|
|
717
|
+
map.entries().filter_map(|entry| entry.value().map(|value| value.syntax().clone())).collect()
|
|
718
|
+
} else if let Some(map) = find_flow_map(node) {
|
|
719
|
+
flow_map_entries(&map)
|
|
720
|
+
.into_iter()
|
|
721
|
+
.filter_map(|entry| entry.value().map(|value| value.syntax().clone()))
|
|
722
|
+
.collect()
|
|
684
723
|
} else {
|
|
685
724
|
Vec::new()
|
|
686
725
|
}
|
|
@@ -693,6 +732,16 @@ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment
|
|
|
693
732
|
return vec![value.syntax().clone()];
|
|
694
733
|
}
|
|
695
734
|
}
|
|
735
|
+
|
|
736
|
+
return Vec::new();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if let Some(map) = find_flow_map(node) {
|
|
740
|
+
if let Some(entry) = find_flow_entry_by_key(&map, key) {
|
|
741
|
+
if let Some(value) = entry.value() {
|
|
742
|
+
return vec![value.syntax().clone()];
|
|
743
|
+
}
|
|
744
|
+
}
|
|
696
745
|
}
|
|
697
746
|
|
|
698
747
|
Vec::new()
|
data/rust/src/document/set.rs
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
+
fn holds_collection(node: &SyntaxNode) -> bool {
|
|
4
|
+
node.descendants().any(|descendant| {
|
|
5
|
+
matches!(
|
|
6
|
+
descendant.kind(),
|
|
7
|
+
SyntaxKind::BLOCK_MAP | SyntaxKind::BLOCK_SEQ | SyntaxKind::FLOW_MAP | SyntaxKind::FLOW_SEQ
|
|
8
|
+
)
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
struct ValueSpan {
|
|
13
|
+
range: TextRange,
|
|
14
|
+
separated: bool,
|
|
15
|
+
comment: Option<String>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn value_span(node: &SyntaxNode) -> Option<ValueSpan> {
|
|
19
|
+
let (container, separator_kind) = match node.kind() {
|
|
20
|
+
SyntaxKind::BLOCK_MAP_VALUE | SyntaxKind::FLOW_MAP_VALUE => (node.parent()?, SyntaxKind::COLON),
|
|
21
|
+
SyntaxKind::BLOCK_SEQ_ENTRY => (node.clone(), SyntaxKind::MINUS),
|
|
22
|
+
|
|
23
|
+
SyntaxKind::FLOW_SEQ_ENTRY => {
|
|
24
|
+
return Some(ValueSpan {
|
|
25
|
+
range: node.text_range(),
|
|
26
|
+
separated: false,
|
|
27
|
+
comment: None,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_ => return None,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let mut elements = container
|
|
35
|
+
.children_with_tokens()
|
|
36
|
+
.skip_while(|element| element.as_token().map(|token| token.kind() != separator_kind).unwrap_or(true));
|
|
37
|
+
|
|
38
|
+
let separator = elements.next()?.into_token()?;
|
|
39
|
+
let mut comment = None;
|
|
40
|
+
|
|
41
|
+
for element in elements {
|
|
42
|
+
match element.into_token() {
|
|
43
|
+
Some(token) if token.kind() == SyntaxKind::COMMENT => comment = Some(token.text().trim_end().to_string()),
|
|
44
|
+
Some(token) if token.kind() == SyntaxKind::WHITESPACE && token.text().contains('\n') => break,
|
|
45
|
+
Some(_) => {}
|
|
46
|
+
None => break,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Some(ValueSpan {
|
|
51
|
+
range: TextRange::new(separator.text_range().end(), node.text_range().end()),
|
|
52
|
+
separated: true,
|
|
53
|
+
comment,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn replacement_text(span: &ValueSpan, value: &str) -> String {
|
|
58
|
+
let mut text = if span.separated { format!(" {}", value) } else { value.to_string() };
|
|
59
|
+
|
|
60
|
+
if let Some(comment) = &span.comment {
|
|
61
|
+
text.push(' ');
|
|
62
|
+
text.push_str(comment);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
text
|
|
66
|
+
}
|
|
67
|
+
|
|
3
68
|
impl Document {
|
|
4
69
|
pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
5
70
|
let current_node = self.navigate(dot_path)?;
|
|
@@ -11,6 +76,12 @@ impl Document {
|
|
|
11
76
|
return self.apply_edit(block_scalar.text_range(), &new_text);
|
|
12
77
|
}
|
|
13
78
|
|
|
79
|
+
if holds_collection(¤t_node) {
|
|
80
|
+
let span = value_span(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
81
|
+
|
|
82
|
+
return self.apply_edit(span.range, &replacement_text(&span, value));
|
|
83
|
+
}
|
|
84
|
+
|
|
14
85
|
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
15
86
|
|
|
16
87
|
let new_text = format_scalar_value(value, scalar_token.kind());
|
|
@@ -31,6 +102,10 @@ impl Document {
|
|
|
31
102
|
for node in nodes {
|
|
32
103
|
if let Some(block_scalar) = node.descendants().find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR) {
|
|
33
104
|
edits.push((block_scalar.text_range(), Self::block_scalar_replacement(&source, &block_scalar, value)));
|
|
105
|
+
} else if holds_collection(&node) {
|
|
106
|
+
if let Some(span) = value_span(&node) {
|
|
107
|
+
edits.push((span.range, replacement_text(&span, value)));
|
|
108
|
+
}
|
|
34
109
|
} else if let Some(scalar_token) = find_scalar_token(&node) {
|
|
35
110
|
edits.push((scalar_token.text_range(), format_scalar_value(value, scalar_token.kind())));
|
|
36
111
|
}
|
|
@@ -68,6 +143,12 @@ impl Document {
|
|
|
68
143
|
return self.apply_edit(range, value);
|
|
69
144
|
}
|
|
70
145
|
|
|
146
|
+
if holds_collection(¤t_node) {
|
|
147
|
+
let span = value_span(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
148
|
+
|
|
149
|
+
return self.apply_edit(span.range, &replacement_text(&span, value));
|
|
150
|
+
}
|
|
151
|
+
|
|
71
152
|
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
72
153
|
|
|
73
154
|
self.replace_token(&scalar_token, value)
|
data/rust/src/error.rs
CHANGED
|
@@ -4,7 +4,9 @@ pub enum YerbaError {
|
|
|
4
4
|
IoError(std::io::Error),
|
|
5
5
|
SelectorNotFound(String),
|
|
6
6
|
AmbiguousSelector(String, usize),
|
|
7
|
+
InvalidCondition(String, String),
|
|
7
8
|
NotASequence(String),
|
|
9
|
+
FlowCollectionNotWritable(String),
|
|
8
10
|
IndexOutOfBounds(usize, usize),
|
|
9
11
|
UnknownKeys(Vec<String>),
|
|
10
12
|
DuplicateValues(Vec<crate::DuplicateInfo>),
|
|
@@ -25,6 +27,14 @@ impl std::fmt::Display for YerbaError {
|
|
|
25
27
|
YerbaError::SelectorNotFound(selector) => write!(f, "selector not found: {}", selector),
|
|
26
28
|
YerbaError::NotASequence(path) => write!(f, "not a sequence: {}", path),
|
|
27
29
|
|
|
30
|
+
YerbaError::FlowCollectionNotWritable(path) => {
|
|
31
|
+
write!(
|
|
32
|
+
f,
|
|
33
|
+
"cannot write to \"{}\": flow collections are not writable yet. Convert it to block style first, with `collection_style` or `yerba collection-style`",
|
|
34
|
+
path
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
YerbaError::AmbiguousSelector(selector, count) => {
|
|
29
39
|
write!(
|
|
30
40
|
f,
|
|
@@ -33,6 +43,10 @@ impl std::fmt::Display for YerbaError {
|
|
|
33
43
|
)
|
|
34
44
|
}
|
|
35
45
|
|
|
46
|
+
YerbaError::InvalidCondition(condition, reason) => {
|
|
47
|
+
write!(f, "invalid condition \"{}\": {}", condition, reason)
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
YerbaError::DuplicateKey {
|
|
37
51
|
key,
|
|
38
52
|
first_line,
|
data/rust/src/ffi.rs
CHANGED
|
@@ -330,6 +330,24 @@ pub unsafe extern "C" fn yerba_document_resolve_selectors(document: *const Docum
|
|
|
330
330
|
CString::new(json_string).unwrap_or_default().into_raw()
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
#[no_mangle]
|
|
334
|
+
pub unsafe extern "C" fn yerba_selector_has_wildcard(path: *const c_char) -> bool {
|
|
335
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
336
|
+
|
|
337
|
+
crate::selector::Selector::parse(selector_string).has_wildcard()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[no_mangle]
|
|
341
|
+
pub unsafe extern "C" fn yerba_document_keys(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
342
|
+
let document = &*document;
|
|
343
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
344
|
+
|
|
345
|
+
let keys = document.keys_at(selector_string);
|
|
346
|
+
let json_string = serde_json::to_string(&keys).unwrap_or_else(|_| "[]".to_string());
|
|
347
|
+
|
|
348
|
+
CString::new(json_string).unwrap_or_default().into_raw()
|
|
349
|
+
}
|
|
350
|
+
|
|
333
351
|
#[no_mangle]
|
|
334
352
|
pub unsafe extern "C" fn yerba_document_get_quote_style(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
335
353
|
let document = &*document;
|
|
@@ -409,12 +427,33 @@ pub unsafe extern "C" fn yerba_document_set_sequence_indent(document: *mut Docum
|
|
|
409
427
|
}
|
|
410
428
|
}
|
|
411
429
|
|
|
430
|
+
#[no_mangle]
|
|
431
|
+
pub unsafe extern "C" fn yerba_validate_condition(condition: *const c_char, item_context: bool) -> *mut c_char {
|
|
432
|
+
if condition.is_null() {
|
|
433
|
+
return ptr::null_mut();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let cond = CStr::from_ptr(condition).to_str().unwrap_or("");
|
|
437
|
+
|
|
438
|
+
let result = if item_context {
|
|
439
|
+
crate::validate_item_condition(cond)
|
|
440
|
+
} else {
|
|
441
|
+
crate::validate_condition(cond)
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
match result {
|
|
445
|
+
Ok(()) => ptr::null_mut(),
|
|
446
|
+
Err(error) => CString::new(error.to_string()).unwrap_or_default().into_raw(),
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
412
450
|
#[no_mangle]
|
|
413
451
|
pub unsafe extern "C" fn yerba_document_evaluate_condition(document: *const Document, parent_path: *const c_char, condition: *const c_char) -> bool {
|
|
414
452
|
let document = &*document;
|
|
415
453
|
let parent = CStr::from_ptr(parent_path).to_str().unwrap_or("");
|
|
416
454
|
let cond = CStr::from_ptr(condition).to_str().unwrap_or("");
|
|
417
|
-
|
|
455
|
+
|
|
456
|
+
document.evaluate_condition(parent, cond).unwrap_or(false)
|
|
418
457
|
}
|
|
419
458
|
|
|
420
459
|
#[no_mangle]
|
|
@@ -440,7 +479,7 @@ pub unsafe extern "C" fn yerba_document_find(document: *const Document, path: *c
|
|
|
440
479
|
|
|
441
480
|
let select_string = if select.is_null() { None } else { CStr::from_ptr(select).to_str().ok() };
|
|
442
481
|
|
|
443
|
-
let results = document.find_items(selector_string, condition_string, select_string);
|
|
482
|
+
let results = document.find_items(selector_string, condition_string, select_string).unwrap_or_default();
|
|
444
483
|
let json = serde_json::to_string_pretty(&results).unwrap_or_else(|_| "[]".to_string());
|
|
445
484
|
|
|
446
485
|
CString::new(json).unwrap_or_default().into_raw()
|
|
@@ -830,12 +869,7 @@ pub unsafe extern "C" fn yerba_get_result_free(result: YerbaGetResult) {
|
|
|
830
869
|
}
|
|
831
870
|
}
|
|
832
871
|
|
|
833
|
-
|
|
834
|
-
pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
|
|
835
|
-
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
836
|
-
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
837
|
-
let nodes = crate::glob_get(pattern, selector_string);
|
|
838
|
-
|
|
872
|
+
fn located_nodes_to_list(nodes: &[crate::LocatedNode]) -> YerbaTypedList {
|
|
839
873
|
let results: Vec<serde_json::Value> = nodes
|
|
840
874
|
.iter()
|
|
841
875
|
.map(|node| {
|
|
@@ -865,6 +899,18 @@ pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *cons
|
|
|
865
899
|
value["type"] = serde_json::json!(vt as u8);
|
|
866
900
|
}
|
|
867
901
|
|
|
902
|
+
if let Some(key_name) = &node.key_name {
|
|
903
|
+
value["key_name"] = serde_json::json!(key_name);
|
|
904
|
+
value["key_location"] = serde_json::json!({
|
|
905
|
+
"start_line": node.key_location.start_line,
|
|
906
|
+
"start_column": node.key_location.start_column,
|
|
907
|
+
"end_line": node.key_location.end_line,
|
|
908
|
+
"end_column": node.key_location.end_column,
|
|
909
|
+
"start_offset": node.key_location.start_offset,
|
|
910
|
+
"end_offset": node.key_location.end_offset,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
868
914
|
value
|
|
869
915
|
})
|
|
870
916
|
.collect();
|
|
@@ -878,6 +924,22 @@ pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *cons
|
|
|
878
924
|
}
|
|
879
925
|
}
|
|
880
926
|
|
|
927
|
+
#[no_mangle]
|
|
928
|
+
pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
|
|
929
|
+
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
930
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
931
|
+
|
|
932
|
+
located_nodes_to_list(&crate::glob_get(pattern, selector_string))
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
#[no_mangle]
|
|
936
|
+
pub unsafe extern "C" fn yerba_document_get_all(document: *const Document, path: *const c_char) -> YerbaTypedList {
|
|
937
|
+
let document = &*document;
|
|
938
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
939
|
+
|
|
940
|
+
located_nodes_to_list(&document.get_all_located(selector_string))
|
|
941
|
+
}
|
|
942
|
+
|
|
881
943
|
#[no_mangle]
|
|
882
944
|
pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *const c_char, condition: *const c_char, select: *const c_char) -> YerbaTypedList {
|
|
883
945
|
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
@@ -885,7 +947,7 @@ pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *con
|
|
|
885
947
|
let condition_string = if condition.is_null() { None } else { CStr::from_ptr(condition).to_str().ok() };
|
|
886
948
|
let select_string = if select.is_null() { None } else { CStr::from_ptr(select).to_str().ok() };
|
|
887
949
|
|
|
888
|
-
let all_results = crate::glob_find(pattern, selector_string, condition_string, select_string);
|
|
950
|
+
let all_results = crate::glob_find(pattern, selector_string, condition_string, select_string).unwrap_or_default();
|
|
889
951
|
let length = all_results.len();
|
|
890
952
|
let json = serde_json::to_string_pretty(&all_results).unwrap_or_else(|_| "[]".to_string());
|
|
891
953
|
|