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/rust/src/document/get.rs
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
3
|
impl Document {
|
|
4
|
+
pub fn is_valid_selector(&self, dot_path: &str) -> bool {
|
|
5
|
+
if dot_path.is_empty() {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let selectors = self.selectors();
|
|
10
|
+
|
|
11
|
+
if selectors.contains(&dot_path.to_string()) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let wildcard_version = dot_path.replace(|c: char| c.is_ascii_digit(), "").replace("[.", "[");
|
|
16
|
+
|
|
17
|
+
let mut normalized = String::new();
|
|
18
|
+
let mut chars = dot_path.chars().peekable();
|
|
19
|
+
|
|
20
|
+
while let Some(char) = chars.next() {
|
|
21
|
+
if char == '[' {
|
|
22
|
+
normalized.push('[');
|
|
23
|
+
|
|
24
|
+
while chars.peek().map(|char| char.is_ascii_digit()).unwrap_or(false) {
|
|
25
|
+
chars.next();
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
normalized.push(char);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
selectors.contains(&normalized) || selectors.contains(&wildcard_version)
|
|
33
|
+
}
|
|
34
|
+
|
|
4
35
|
pub fn get(&self, dot_path: &str) -> Option<String> {
|
|
5
36
|
if dot_path.contains('[') {
|
|
6
37
|
return self.get_all(dot_path).into_iter().next();
|
|
@@ -12,7 +43,7 @@ impl Document {
|
|
|
12
43
|
}
|
|
13
44
|
|
|
14
45
|
pub fn get_all(&self, dot_path: &str) -> Vec<String> {
|
|
15
|
-
self.
|
|
46
|
+
self.navigate_all_compact(dot_path).iter().filter_map(extract_scalar_text).collect()
|
|
16
47
|
}
|
|
17
48
|
|
|
18
49
|
pub fn get_typed(&self, dot_path: &str) -> Option<ScalarValue> {
|
|
@@ -34,7 +65,7 @@ impl Document {
|
|
|
34
65
|
|
|
35
66
|
pub fn get_all_typed(&self, dot_path: &str) -> Vec<ScalarValue> {
|
|
36
67
|
self
|
|
37
|
-
.
|
|
68
|
+
.navigate_all_compact(dot_path)
|
|
38
69
|
.iter()
|
|
39
70
|
.filter(|node| {
|
|
40
71
|
!node
|
|
@@ -45,6 +76,59 @@ impl Document {
|
|
|
45
76
|
.collect()
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
pub fn resolve_selectors(&self, dot_path: &str) -> Vec<String> {
|
|
80
|
+
self.navigate_all_compact(dot_path).iter().map(node_selector).collect()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn get_all_located(&self, dot_path: &str) -> Vec<LocatedNode> {
|
|
84
|
+
let source = self.root.text().to_string();
|
|
85
|
+
let file_path = self.path.as_ref().map(|p| p.to_string_lossy().to_string());
|
|
86
|
+
|
|
87
|
+
self
|
|
88
|
+
.navigate_all_compact(dot_path)
|
|
89
|
+
.iter()
|
|
90
|
+
.map(|node| {
|
|
91
|
+
let offset: usize = node.text_range().start().into();
|
|
92
|
+
let line = source[..offset].matches('\n').count() + 1;
|
|
93
|
+
let selector = node_selector(node);
|
|
94
|
+
let is_scalar = !node
|
|
95
|
+
.descendants()
|
|
96
|
+
.any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ);
|
|
97
|
+
|
|
98
|
+
let (text, value_type) = if is_scalar {
|
|
99
|
+
extract_scalar(node)
|
|
100
|
+
.map(|s| (Some(s.text.clone()), Some(crate::syntax::detect_yaml_type(&s))))
|
|
101
|
+
.unwrap_or((None, None))
|
|
102
|
+
} else {
|
|
103
|
+
(None, None)
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let node_type = if is_scalar {
|
|
107
|
+
"scalar"
|
|
108
|
+
} else {
|
|
109
|
+
let map_pos = node.descendants().find_map(BlockMap::cast).map(|m| m.syntax().text_range().start());
|
|
110
|
+
let seq_pos = node.descendants().find_map(BlockSeq::cast).map(|s| s.syntax().text_range().start());
|
|
111
|
+
|
|
112
|
+
match (map_pos, seq_pos) {
|
|
113
|
+
(Some(m), Some(s)) if s < m => "sequence",
|
|
114
|
+
(Some(_), _) => "map",
|
|
115
|
+
(None, Some(_)) => "sequence",
|
|
116
|
+
_ => "map",
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
LocatedNode {
|
|
121
|
+
node_type: node_type.to_string(),
|
|
122
|
+
text,
|
|
123
|
+
value_type,
|
|
124
|
+
file_path: file_path.clone(),
|
|
125
|
+
selector,
|
|
126
|
+
line,
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
.collect()
|
|
130
|
+
}
|
|
131
|
+
|
|
48
132
|
pub fn node_type(&self, dot_path: &str) -> NodeType {
|
|
49
133
|
match self.navigate(dot_path) {
|
|
50
134
|
Ok(node) => {
|
|
@@ -97,15 +181,43 @@ impl Document {
|
|
|
97
181
|
key_name,
|
|
98
182
|
key_location,
|
|
99
183
|
},
|
|
100
|
-
None =>
|
|
101
|
-
node_type
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
184
|
+
None => {
|
|
185
|
+
let (node_type, entry_location, entry_key_name, entry_key_location) = if self.navigate(dot_path).is_err() {
|
|
186
|
+
if let Some(entry_node) = self.find_entry(dot_path) {
|
|
187
|
+
let range = entry_node.text_range();
|
|
188
|
+
let entry_location = compute_location(&source, range.start().into(), range.end().into());
|
|
189
|
+
|
|
190
|
+
let (node, location) = yaml_parser::ast::BlockMapEntry::cast(entry_node.clone())
|
|
191
|
+
.and_then(|entry| {
|
|
192
|
+
entry.key().and_then(|key_node| {
|
|
193
|
+
let key_text = extract_scalar_text(key_node.syntax())?;
|
|
194
|
+
let key_range = key_node.syntax().text_range();
|
|
195
|
+
let key_location = compute_location(&source, key_range.start().into(), key_range.end().into());
|
|
196
|
+
|
|
197
|
+
Some((key_text, key_location))
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
.map(|(name, location)| (Some(name), location))
|
|
201
|
+
.unwrap_or((None, Location::default()));
|
|
202
|
+
|
|
203
|
+
(NodeType::Scalar, entry_location, node, location)
|
|
204
|
+
} else {
|
|
205
|
+
(self.node_type(dot_path), location, key_name, key_location)
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
(self.node_type(dot_path), location, key_name, key_location)
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
NodeInfo {
|
|
212
|
+
node_type,
|
|
213
|
+
is_list: false,
|
|
214
|
+
value: None,
|
|
215
|
+
list_values: vec![],
|
|
216
|
+
location: entry_location,
|
|
217
|
+
key_name: entry_key_name,
|
|
218
|
+
key_location: entry_key_location,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
109
221
|
}
|
|
110
222
|
}
|
|
111
223
|
|
|
@@ -171,23 +283,56 @@ impl Document {
|
|
|
171
283
|
return Some(node_to_yaml_value(&self.root));
|
|
172
284
|
}
|
|
173
285
|
|
|
174
|
-
let
|
|
286
|
+
let parsed = crate::selector::Selector::parse(dot_path);
|
|
175
287
|
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
}
|
|
288
|
+
if parsed.has_wildcard() {
|
|
289
|
+
let padded = self.navigate_all(dot_path);
|
|
179
290
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
291
|
+
if padded.is_empty() {
|
|
292
|
+
return None;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let values: Vec<serde_yaml::Value> = padded
|
|
296
|
+
.iter()
|
|
297
|
+
.map(|maybe_node| match maybe_node {
|
|
298
|
+
Some(node) => node_to_yaml_value(node),
|
|
299
|
+
None => serde_yaml::Value::Null,
|
|
300
|
+
})
|
|
301
|
+
.collect();
|
|
302
|
+
|
|
303
|
+
Some(serde_yaml::Value::Sequence(values))
|
|
304
|
+
} else {
|
|
305
|
+
let nodes = self.navigate_all_compact(dot_path);
|
|
183
306
|
|
|
184
|
-
|
|
307
|
+
if nodes.is_empty() {
|
|
308
|
+
return None;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if nodes.len() == 1 {
|
|
312
|
+
return Some(node_to_yaml_value(&nodes[0]));
|
|
313
|
+
}
|
|
185
314
|
|
|
186
|
-
|
|
315
|
+
let values: Vec<serde_yaml::Value> = nodes.iter().map(node_to_yaml_value).collect();
|
|
316
|
+
|
|
317
|
+
Some(serde_yaml::Value::Sequence(values))
|
|
318
|
+
}
|
|
187
319
|
}
|
|
188
320
|
|
|
189
321
|
pub fn get_values(&self, dot_path: &str) -> Vec<serde_yaml::Value> {
|
|
190
|
-
|
|
322
|
+
let parsed = crate::selector::Selector::parse(dot_path);
|
|
323
|
+
|
|
324
|
+
if parsed.has_wildcard() {
|
|
325
|
+
self
|
|
326
|
+
.navigate_all(dot_path)
|
|
327
|
+
.iter()
|
|
328
|
+
.map(|maybe_node| match maybe_node {
|
|
329
|
+
Some(node) => node_to_yaml_value(node),
|
|
330
|
+
None => serde_yaml::Value::Null,
|
|
331
|
+
})
|
|
332
|
+
.collect()
|
|
333
|
+
} else {
|
|
334
|
+
self.navigate_all_compact(dot_path).iter().map(node_to_yaml_value).collect()
|
|
335
|
+
}
|
|
191
336
|
}
|
|
192
337
|
|
|
193
338
|
pub fn selectors(&self) -> Vec<String> {
|
|
@@ -206,10 +351,33 @@ impl Document {
|
|
|
206
351
|
|
|
207
352
|
pub fn exists(&self, dot_path: &str) -> bool {
|
|
208
353
|
if dot_path.contains('[') {
|
|
209
|
-
|
|
354
|
+
if !self.navigate_all_compact(dot_path).is_empty() {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
} else if self.navigate(dot_path).is_ok() {
|
|
358
|
+
return true;
|
|
210
359
|
}
|
|
211
360
|
|
|
212
|
-
self.
|
|
361
|
+
self.find_entry(dot_path).is_some()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn find_entry(&self, dot_path: &str) -> Option<SyntaxNode> {
|
|
365
|
+
let (parent_path, last_key) = match dot_path.rsplit_once('.') {
|
|
366
|
+
Some((parent, key)) => (parent, key),
|
|
367
|
+
None => ("", dot_path),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
let parent_node = if parent_path.is_empty() {
|
|
371
|
+
let root = Root::cast(self.root.clone())?;
|
|
372
|
+
let document = root.documents().next()?;
|
|
373
|
+
document.syntax().clone()
|
|
374
|
+
} else {
|
|
375
|
+
self.navigate(parent_path).ok()?
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
let map = parent_node.descendants().find_map(BlockMap::cast)?;
|
|
379
|
+
|
|
380
|
+
find_entry_by_key(&map, last_key).map(|entry| entry.syntax().clone())
|
|
213
381
|
}
|
|
214
382
|
|
|
215
383
|
pub fn get_sequence_values(&self, dot_path: &str) -> Vec<String> {
|
|
@@ -260,3 +428,66 @@ impl Document {
|
|
|
260
428
|
None
|
|
261
429
|
}
|
|
262
430
|
}
|
|
431
|
+
|
|
432
|
+
pub(crate) fn node_selector(node: &SyntaxNode) -> String {
|
|
433
|
+
let mut parts: Vec<String> = Vec::new();
|
|
434
|
+
let mut current = node.clone();
|
|
435
|
+
|
|
436
|
+
if current.kind() == SyntaxKind::BLOCK_SEQ_ENTRY {
|
|
437
|
+
if let Some(parent) = current.parent() {
|
|
438
|
+
let index = parent
|
|
439
|
+
.children()
|
|
440
|
+
.filter(|child| child.kind() == SyntaxKind::BLOCK_SEQ_ENTRY)
|
|
441
|
+
.position(|child| child == current)
|
|
442
|
+
.unwrap_or(0);
|
|
443
|
+
|
|
444
|
+
parts.push(format!("[{}]", index));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
loop {
|
|
449
|
+
let parent = match current.parent() {
|
|
450
|
+
Some(parent) => parent,
|
|
451
|
+
None => break,
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
match parent.kind() {
|
|
455
|
+
SyntaxKind::BLOCK_SEQ_ENTRY => {
|
|
456
|
+
if let Some(grandparent) = parent.parent() {
|
|
457
|
+
let index = grandparent
|
|
458
|
+
.children()
|
|
459
|
+
.filter(|child| child.kind() == SyntaxKind::BLOCK_SEQ_ENTRY)
|
|
460
|
+
.position(|child| child == parent)
|
|
461
|
+
.unwrap_or(0);
|
|
462
|
+
|
|
463
|
+
parts.push(format!("[{}]", index));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
SyntaxKind::BLOCK_MAP_ENTRY => {
|
|
468
|
+
if let Some(key_node) = parent.children().find(|child| child.kind() == SyntaxKind::BLOCK_MAP_KEY) {
|
|
469
|
+
if let Some(key_text) = extract_scalar_text(&key_node) {
|
|
470
|
+
parts.push(key_text);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_ => {}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
current = parent;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
parts.reverse();
|
|
482
|
+
let mut result = String::new();
|
|
483
|
+
|
|
484
|
+
for part in &parts {
|
|
485
|
+
if !part.starts_with('[') && !result.is_empty() {
|
|
486
|
+
result.push('.');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
result.push_str(part);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
result
|
|
493
|
+
}
|
data/rust/src/document/mod.rs
CHANGED
|
@@ -2,9 +2,24 @@ mod condition;
|
|
|
2
2
|
mod delete;
|
|
3
3
|
mod get;
|
|
4
4
|
mod insert;
|
|
5
|
+
mod schema;
|
|
5
6
|
mod set;
|
|
6
7
|
mod sort;
|
|
7
8
|
mod style;
|
|
9
|
+
mod unique;
|
|
10
|
+
pub use unique::DuplicateInfo;
|
|
11
|
+
|
|
12
|
+
use crate::syntax::YerbaValueType;
|
|
13
|
+
|
|
14
|
+
#[derive(Debug, Clone)]
|
|
15
|
+
pub struct LocatedNode {
|
|
16
|
+
pub node_type: String,
|
|
17
|
+
pub text: Option<String>,
|
|
18
|
+
pub value_type: Option<YerbaValueType>,
|
|
19
|
+
pub file_path: Option<String>,
|
|
20
|
+
pub selector: String,
|
|
21
|
+
pub line: usize,
|
|
22
|
+
}
|
|
8
23
|
|
|
9
24
|
use std::fs;
|
|
10
25
|
use std::path::{Path, PathBuf};
|
|
@@ -19,8 +34,8 @@ use crate::error::YerbaError;
|
|
|
19
34
|
use crate::QuoteStyle;
|
|
20
35
|
|
|
21
36
|
use crate::syntax::{
|
|
22
|
-
extract_scalar, extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key, is_yaml_non_string,
|
|
23
|
-
removal_range, unescape_double_quoted, unescape_single_quoted, ScalarValue,
|
|
37
|
+
dedent_block_scalar, extract_scalar, extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key, is_yaml_non_string,
|
|
38
|
+
preceding_whitespace_indent, removal_range, unescape_double_quoted, unescape_single_quoted, ScalarValue,
|
|
24
39
|
};
|
|
25
40
|
|
|
26
41
|
#[derive(Debug, Clone)]
|
|
@@ -155,7 +170,7 @@ impl Document {
|
|
|
155
170
|
return Ok(document.syntax().clone());
|
|
156
171
|
}
|
|
157
172
|
|
|
158
|
-
let nodes = self.
|
|
173
|
+
let nodes = self.navigate_all_compact(dot_path);
|
|
159
174
|
|
|
160
175
|
match nodes.len() {
|
|
161
176
|
0 => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
|
|
@@ -164,7 +179,11 @@ impl Document {
|
|
|
164
179
|
}
|
|
165
180
|
}
|
|
166
181
|
|
|
167
|
-
pub fn
|
|
182
|
+
pub fn navigate_all_compact(&self, dot_path: &str) -> Vec<SyntaxNode> {
|
|
183
|
+
self.navigate_all(dot_path).into_iter().flatten().collect()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub fn navigate_all(&self, dot_path: &str) -> Vec<Option<SyntaxNode>> {
|
|
168
187
|
if Document::validate_path(dot_path).is_err() {
|
|
169
188
|
return Vec::new();
|
|
170
189
|
}
|
|
@@ -181,26 +200,63 @@ impl Document {
|
|
|
181
200
|
None => return Vec::new(),
|
|
182
201
|
};
|
|
183
202
|
|
|
184
|
-
let mut current_nodes = vec![document.syntax().clone()];
|
|
203
|
+
let mut current_nodes: Vec<Option<SyntaxNode>> = vec![Some(document.syntax().clone())];
|
|
185
204
|
|
|
186
205
|
if parsed.is_empty() {
|
|
187
206
|
if let Some(sequence) = document.syntax().descendants().find_map(BlockSeq::cast) {
|
|
188
|
-
current_nodes = sequence.entries().map(|entry| entry.syntax().clone()).collect();
|
|
207
|
+
current_nodes = sequence.entries().map(|entry| Some(entry.syntax().clone())).collect();
|
|
189
208
|
}
|
|
190
209
|
|
|
191
210
|
return current_nodes;
|
|
192
211
|
}
|
|
193
212
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
let segments = parsed.segments();
|
|
214
|
+
|
|
215
|
+
for (i, segment) in segments.iter().enumerate() {
|
|
216
|
+
let is_wildcard = matches!(segment, crate::selector::SelectorSegment::AllItems);
|
|
217
|
+
let has_remaining = i + 1 < segments.len();
|
|
218
|
+
let mut next_nodes: Vec<Option<SyntaxNode>> = Vec::new();
|
|
219
|
+
|
|
220
|
+
for maybe_node in ¤t_nodes {
|
|
221
|
+
match maybe_node {
|
|
222
|
+
None => next_nodes.push(None),
|
|
223
|
+
Some(node) => {
|
|
224
|
+
let resolved = resolve_segment(node, segment);
|
|
225
|
+
|
|
226
|
+
if is_wildcard && has_remaining {
|
|
227
|
+
let remaining = &segments[i + 1..];
|
|
228
|
+
|
|
229
|
+
for item in &resolved {
|
|
230
|
+
let results = navigate_remaining(item, remaining);
|
|
231
|
+
|
|
232
|
+
if results.is_empty() {
|
|
233
|
+
next_nodes.push(None);
|
|
234
|
+
} else {
|
|
235
|
+
for result in results {
|
|
236
|
+
next_nodes.push(Some(result));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return next_nodes;
|
|
242
|
+
} else if is_wildcard {
|
|
243
|
+
for item in resolved {
|
|
244
|
+
next_nodes.push(Some(item));
|
|
245
|
+
}
|
|
246
|
+
} else if resolved.is_empty() {
|
|
247
|
+
return Vec::new();
|
|
248
|
+
} else {
|
|
249
|
+
for item in resolved {
|
|
250
|
+
next_nodes.push(Some(item));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
199
255
|
}
|
|
200
256
|
|
|
201
257
|
current_nodes = next_nodes;
|
|
202
258
|
|
|
203
|
-
if current_nodes.
|
|
259
|
+
if current_nodes.iter().all(|n| n.is_none()) {
|
|
204
260
|
break;
|
|
205
261
|
}
|
|
206
262
|
}
|
|
@@ -463,6 +519,8 @@ pub(crate) fn node_to_yaml_value(node: &SyntaxNode) -> serde_yaml::Value {
|
|
|
463
519
|
.map(|token| token.text().to_string())
|
|
464
520
|
.unwrap_or_default();
|
|
465
521
|
|
|
522
|
+
let text = dedent_block_scalar(&text);
|
|
523
|
+
|
|
466
524
|
return serde_yaml::Value::String(text);
|
|
467
525
|
}
|
|
468
526
|
|
|
@@ -516,6 +574,26 @@ pub(crate) fn parse_condition(condition: &str) -> Option<(String, &str, String)>
|
|
|
516
574
|
Some((left.to_string(), operator, right.to_string()))
|
|
517
575
|
}
|
|
518
576
|
|
|
577
|
+
fn navigate_remaining(node: &SyntaxNode, segments: &[crate::selector::SelectorSegment]) -> Vec<SyntaxNode> {
|
|
578
|
+
let mut current_nodes = vec![node.clone()];
|
|
579
|
+
|
|
580
|
+
for segment in segments {
|
|
581
|
+
let mut next_nodes = Vec::new();
|
|
582
|
+
|
|
583
|
+
for current in ¤t_nodes {
|
|
584
|
+
next_nodes.extend(resolve_segment(current, segment));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if next_nodes.is_empty() {
|
|
588
|
+
return Vec::new();
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
current_nodes = next_nodes;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
current_nodes
|
|
595
|
+
}
|
|
596
|
+
|
|
519
597
|
fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment) -> Vec<SyntaxNode> {
|
|
520
598
|
use crate::selector::SelectorSegment;
|
|
521
599
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn validate_schema(&self, schema: &serde_json::Value, items: bool, selector: Option<&str>) -> Vec<crate::schema::ValidationError> {
|
|
5
|
+
let path = selector.unwrap_or("");
|
|
6
|
+
let has_wildcard = crate::selector::Selector::parse(path).has_wildcard();
|
|
7
|
+
|
|
8
|
+
let value = match self.get_value(path) {
|
|
9
|
+
Some(value) => crate::json::yaml_to_json(&value),
|
|
10
|
+
None => return vec![],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let validate_items = items || has_wildcard;
|
|
14
|
+
|
|
15
|
+
let mut errors = if validate_items {
|
|
16
|
+
match value.as_array() {
|
|
17
|
+
Some(array) => crate::schema::validate_array(array, schema),
|
|
18
|
+
None if value.is_null() => vec![crate::schema::ValidationError {
|
|
19
|
+
path: String::new(),
|
|
20
|
+
message: "expected an array but document is empty or not a sequence".to_string(),
|
|
21
|
+
item_label: None,
|
|
22
|
+
line: None,
|
|
23
|
+
}],
|
|
24
|
+
None => crate::schema::validate_array(std::slice::from_ref(&value), schema),
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
if value.is_null() {
|
|
28
|
+
return vec![];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
crate::schema::validate_value(&value, schema)
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let source = self.to_string();
|
|
35
|
+
|
|
36
|
+
for error in &mut errors {
|
|
37
|
+
if !error.path.is_empty() {
|
|
38
|
+
let selector = json_pointer_to_selector(&error.path);
|
|
39
|
+
|
|
40
|
+
if let Ok(node) = self.navigate(&selector) {
|
|
41
|
+
let offset: usize = node.text_range().start().into();
|
|
42
|
+
let line = source[..offset].chars().filter(|c| *c == '\n').count() + 1;
|
|
43
|
+
|
|
44
|
+
error.line = Some(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
errors
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn json_pointer_to_selector(pointer: &str) -> String {
|
|
54
|
+
pointer
|
|
55
|
+
.strip_prefix('/')
|
|
56
|
+
.unwrap_or(pointer)
|
|
57
|
+
.split('/')
|
|
58
|
+
.map(|segment| {
|
|
59
|
+
if let Ok(index) = segment.parse::<usize>() {
|
|
60
|
+
format!("[{}]", index)
|
|
61
|
+
} else {
|
|
62
|
+
segment.to_string()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.fold(String::new(), |mut path, segment| {
|
|
66
|
+
if !path.is_empty() && !segment.starts_with('[') {
|
|
67
|
+
path.push('.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
path.push_str(&segment);
|
|
71
|
+
path
|
|
72
|
+
})
|
|
73
|
+
}
|
data/rust/src/document/set.rs
CHANGED
|
@@ -26,7 +26,7 @@ impl Document {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
29
|
-
let nodes = self.
|
|
29
|
+
let nodes = self.navigate_all_compact(dot_path);
|
|
30
30
|
|
|
31
31
|
if nodes.is_empty() {
|
|
32
32
|
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
data/rust/src/document/sort.rs
CHANGED
|
@@ -113,12 +113,15 @@ impl Document {
|
|
|
113
113
|
return self.validate_each_sort_keys(sequence_path, key_order);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
let current_node = self.navigate(dot_path)
|
|
116
|
+
let current_node = match self.navigate(dot_path) {
|
|
117
|
+
Ok(node) => node,
|
|
118
|
+
Err(_) => return Ok(()),
|
|
119
|
+
};
|
|
117
120
|
|
|
118
|
-
let map = current_node
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
let map = match current_node.descendants().find_map(BlockMap::cast) {
|
|
122
|
+
Some(map) => map,
|
|
123
|
+
None => return Ok(()),
|
|
124
|
+
};
|
|
122
125
|
|
|
123
126
|
let unknown_keys: Vec<String> = map
|
|
124
127
|
.entries()
|
|
@@ -138,12 +141,15 @@ impl Document {
|
|
|
138
141
|
return self.sort_each_keys(sequence_path, key_order);
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
let current_node = self.navigate(dot_path)
|
|
144
|
+
let current_node = match self.navigate(dot_path) {
|
|
145
|
+
Ok(node) => node,
|
|
146
|
+
Err(_) => return Ok(()),
|
|
147
|
+
};
|
|
142
148
|
|
|
143
|
-
let map = current_node
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
let map = match current_node.descendants().find_map(BlockMap::cast) {
|
|
150
|
+
Some(map) => map,
|
|
151
|
+
None => return Ok(()),
|
|
152
|
+
};
|
|
147
153
|
|
|
148
154
|
let entries: Vec<_> = map.entries().collect();
|
|
149
155
|
|
|
@@ -202,7 +208,7 @@ impl Document {
|
|
|
202
208
|
Err(_) => return Ok(()),
|
|
203
209
|
}
|
|
204
210
|
} else {
|
|
205
|
-
let found = self.
|
|
211
|
+
let found = self.navigate_all_compact(dot_path);
|
|
206
212
|
if found.is_empty() {
|
|
207
213
|
return Ok(());
|
|
208
214
|
}
|
|
@@ -307,7 +313,7 @@ impl Document {
|
|
|
307
313
|
Err(_) => return Ok(()),
|
|
308
314
|
}
|
|
309
315
|
} else {
|
|
310
|
-
let found = self.
|
|
316
|
+
let found = self.navigate_all_compact(dot_path);
|
|
311
317
|
if found.is_empty() {
|
|
312
318
|
return Ok(());
|
|
313
319
|
}
|
|
@@ -438,7 +444,7 @@ impl Document {
|
|
|
438
444
|
(dot_path, "")
|
|
439
445
|
};
|
|
440
446
|
|
|
441
|
-
let parent_nodes = self.
|
|
447
|
+
let parent_nodes = self.navigate_all_compact(parent_path);
|
|
442
448
|
let source = self.root.text().to_string();
|
|
443
449
|
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
444
450
|
|
data/rust/src/document/style.rs
CHANGED
|
@@ -3,7 +3,7 @@ use super::*;
|
|
|
3
3
|
impl Document {
|
|
4
4
|
pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
|
|
5
5
|
let nodes = if dot_path.contains('[') {
|
|
6
|
-
self.
|
|
6
|
+
self.navigate_all_compact(dot_path)
|
|
7
7
|
} else {
|
|
8
8
|
vec![self.navigate(dot_path)?]
|
|
9
9
|
};
|
|
@@ -105,7 +105,7 @@ impl Document {
|
|
|
105
105
|
let source = self.root.text().to_string();
|
|
106
106
|
|
|
107
107
|
let scope_ranges: Vec<TextRange> = match dot_path {
|
|
108
|
-
Some(path) if !path.is_empty() => self.
|
|
108
|
+
Some(path) if !path.is_empty() => self.navigate_all_compact(path).iter().map(|node| node.text_range()).collect(),
|
|
109
109
|
_ => vec![self.root.text_range()],
|
|
110
110
|
};
|
|
111
111
|
|
|
@@ -219,7 +219,7 @@ impl Document {
|
|
|
219
219
|
let source = self.root.text().to_string();
|
|
220
220
|
|
|
221
221
|
let scope_ranges: Vec<TextRange> = match dot_path {
|
|
222
|
-
Some(path) if !path.is_empty() => self.
|
|
222
|
+
Some(path) if !path.is_empty() => self.navigate_all_compact(path).iter().map(|node| node.text_range()).collect(),
|
|
223
223
|
_ => vec![self.root.text_range()],
|
|
224
224
|
};
|
|
225
225
|
|