yerba 0.4.2 → 0.5.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +242 -54
  3. data/ext/yerba/include/yerba.h +13 -1
  4. data/ext/yerba/yerba.c +239 -113
  5. data/lib/yerba/document.rb +54 -18
  6. data/lib/yerba/map.rb +55 -43
  7. data/lib/yerba/node.rb +58 -0
  8. data/lib/yerba/scalar.rb +20 -23
  9. data/lib/yerba/sequence.rb +88 -55
  10. data/lib/yerba/version.rb +1 -1
  11. data/lib/yerba.rb +2 -0
  12. data/rust/Cargo.toml +3 -2
  13. data/rust/src/commands/delete.rs +1 -1
  14. data/rust/src/commands/get.rs +47 -12
  15. data/rust/src/commands/insert.rs +1 -1
  16. data/rust/src/commands/location.rs +56 -0
  17. data/rust/src/commands/mod.rs +33 -5
  18. data/rust/src/commands/remove.rs +1 -1
  19. data/rust/src/commands/rename.rs +1 -1
  20. data/rust/src/commands/schema.rs +84 -0
  21. data/rust/src/commands/selectors.rs +4 -4
  22. data/rust/src/commands/set.rs +1 -1
  23. data/rust/src/commands/sort.rs +1 -1
  24. data/rust/src/commands/unique.rs +80 -0
  25. data/rust/src/document/condition.rs +18 -2
  26. data/rust/src/document/delete.rs +52 -8
  27. data/rust/src/document/get.rs +256 -25
  28. data/rust/src/document/insert.rs +3 -3
  29. data/rust/src/document/mod.rs +112 -34
  30. data/rust/src/document/schema.rs +73 -0
  31. data/rust/src/document/set.rs +1 -1
  32. data/rust/src/document/sort.rs +21 -15
  33. data/rust/src/document/style.rs +3 -3
  34. data/rust/src/document/unique.rs +86 -0
  35. data/rust/src/error.rs +78 -0
  36. data/rust/src/ffi.rs +89 -9
  37. data/rust/src/json.rs +16 -16
  38. data/rust/src/lib.rs +7 -12
  39. data/rust/src/main.rs +2 -0
  40. data/rust/src/schema.rs +93 -0
  41. data/rust/src/selector.rs +16 -0
  42. data/rust/src/syntax.rs +91 -31
  43. data/rust/src/yerbafile.rs +127 -81
  44. metadata +8 -1
@@ -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
+ }
@@ -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.navigate_all(dot_path);
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()));
@@ -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
- .descendants()
120
- .find_map(BlockMap::cast)
121
- .ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
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
- .descendants()
145
- .find_map(BlockMap::cast)
146
- .ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
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.navigate_all(dot_path);
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.navigate_all(dot_path);
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.navigate_all(parent_path);
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
 
@@ -571,10 +577,10 @@ impl Document {
571
577
  };
572
578
 
573
579
  let labels: Vec<String> = match self.get_value(&items_selector) {
574
- Some(serde_yaml::Value::Sequence(sequence)) => sequence
580
+ Some(yaml_serde::Value::Sequence(sequence)) => sequence
575
581
  .iter()
576
582
  .map(|value| match value {
577
- serde_yaml::Value::String(string) => string.clone(),
583
+ yaml_serde::Value::String(string) => string.clone(),
578
584
  _ => String::new(),
579
585
  })
580
586
  .collect(),
@@ -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.navigate_all(dot_path)
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.navigate_all(path).iter().map(|node| node.text_range()).collect(),
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.navigate_all(path).iter().map(|node| node.text_range()).collect(),
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
 
@@ -0,0 +1,86 @@
1
+ use yaml_parser::ast::BlockSeq;
2
+
3
+ use rowan::ast::AstNode;
4
+
5
+ use crate::document::{extract_scalar_text, navigate_from_node, Document};
6
+ use crate::error::YerbaError;
7
+
8
+ #[derive(Debug, Clone)]
9
+ pub struct DuplicateInfo {
10
+ pub value: String,
11
+ pub line: usize,
12
+ }
13
+
14
+ impl Document {
15
+ pub fn unique(&mut self, dot_path: &str, by: &str, remove: bool) -> Result<Vec<DuplicateInfo>, YerbaError> {
16
+ self.unique_with_options(dot_path, by, remove, false)
17
+ }
18
+
19
+ pub fn unique_with_options(&mut self, dot_path: &str, by: &str, remove: bool, allow_blank_duplicates: bool) -> Result<Vec<DuplicateInfo>, YerbaError> {
20
+ let current_node = self.navigate(dot_path)?;
21
+ let source = self.root.text().to_string();
22
+
23
+ let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
24
+ Some(sequence) => sequence,
25
+ None => return Ok(Vec::new()),
26
+ };
27
+
28
+ let entries: Vec<_> = sequence.entries().collect();
29
+
30
+ if entries.len() <= 1 {
31
+ return Ok(Vec::new());
32
+ }
33
+
34
+ let by_is_scalar = by == ".";
35
+
36
+ let labels: Vec<(Option<String>, usize)> = entries
37
+ .iter()
38
+ .map(|entry| {
39
+ let offset: usize = entry.syntax().text_range().start().into();
40
+ let line = source[..offset].matches('\n').count() + 1;
41
+
42
+ let value = if by_is_scalar {
43
+ Some(entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())).unwrap_or_default())
44
+ } else {
45
+ let field = by.strip_prefix('.').unwrap_or(by);
46
+ let nodes = navigate_from_node(entry.syntax(), field);
47
+
48
+ nodes.first().and_then(extract_scalar_text)
49
+ };
50
+
51
+ (value, line)
52
+ })
53
+ .collect();
54
+
55
+ let mut seen = std::collections::HashSet::new();
56
+ let mut duplicate_indices: Vec<usize> = Vec::new();
57
+ let mut duplicates: Vec<DuplicateInfo> = Vec::new();
58
+
59
+ for (index, (label, line)) in labels.iter().enumerate() {
60
+ let label = match label {
61
+ None => continue,
62
+ Some(value) => value,
63
+ };
64
+
65
+ if allow_blank_duplicates && label.is_empty() {
66
+ continue;
67
+ }
68
+
69
+ if !seen.insert(label.clone()) {
70
+ duplicate_indices.push(index);
71
+ duplicates.push(DuplicateInfo {
72
+ value: label.clone(),
73
+ line: *line,
74
+ });
75
+ }
76
+ }
77
+
78
+ if remove && !duplicate_indices.is_empty() {
79
+ for &index in duplicate_indices.iter().rev() {
80
+ self.remove_at(dot_path, index)?;
81
+ }
82
+ }
83
+
84
+ Ok(duplicates)
85
+ }
86
+ }
data/rust/src/error.rs CHANGED
@@ -7,6 +7,8 @@ pub enum YerbaError {
7
7
  NotASequence(String),
8
8
  IndexOutOfBounds(usize, usize),
9
9
  UnknownKeys(Vec<String>),
10
+ DuplicateValues(Vec<crate::DuplicateInfo>),
11
+ SchemaValidation(Vec<crate::schema::ValidationError>),
10
12
  DuplicateKey {
11
13
  key: String,
12
14
  first_line: usize,
@@ -48,10 +50,26 @@ impl std::fmt::Display for YerbaError {
48
50
  )
49
51
  }
50
52
 
53
+ YerbaError::DuplicateValues(duplicates) => {
54
+ let noun = if duplicates.len() == 1 { "duplicate" } else { "duplicates" };
55
+ let details: Vec<String> = duplicates
56
+ .iter()
57
+ .map(|duplicate| format!("\"{}\" (line {})", duplicate.value, duplicate.line))
58
+ .collect();
59
+
60
+ write!(f, "found {} {}: {}", duplicates.len(), noun, details.join(", "))
61
+ }
62
+
51
63
  YerbaError::IndexOutOfBounds(index, length) => {
52
64
  write!(f, "index {} out of bounds (length {})", index, length)
53
65
  }
54
66
 
67
+ YerbaError::SchemaValidation(errors) => {
68
+ let details: Vec<String> = errors.iter().map(|error| error.to_string()).collect();
69
+
70
+ write!(f, "schema validation failed:\n{}", details.join("\n"))
71
+ }
72
+
55
73
  YerbaError::UnknownKeys(keys) => {
56
74
  let suggestion = keys.iter().map(|key| format!("\"{}\"", key)).collect::<Vec<_>>().join(", ");
57
75
 
@@ -71,3 +89,63 @@ impl From<std::io::Error> for YerbaError {
71
89
  YerbaError::IoError(err)
72
90
  }
73
91
  }
92
+
93
+ pub struct GitHubAnnotation {
94
+ pub level: &'static str,
95
+ pub file: String,
96
+ pub line: Option<usize>,
97
+ pub message: String,
98
+ }
99
+
100
+ impl std::fmt::Display for GitHubAnnotation {
101
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102
+ match self.line {
103
+ Some(line) => write!(f, "::{}file={},line={}::{}", self.level, self.file, line, self.message),
104
+ None => write!(f, "::{}file={}::{}", self.level, self.file, self.message),
105
+ }
106
+ }
107
+ }
108
+
109
+ pub trait GitHubAnnotations {
110
+ fn github_annotations(&self, file: &str) -> Vec<GitHubAnnotation>;
111
+ }
112
+
113
+ impl GitHubAnnotations for YerbaError {
114
+ fn github_annotations(&self, file: &str) -> Vec<GitHubAnnotation> {
115
+ match self {
116
+ YerbaError::DuplicateValues(duplicates) => duplicates
117
+ .iter()
118
+ .map(|duplicate| GitHubAnnotation {
119
+ level: "error ",
120
+ file: file.to_string(),
121
+ line: Some(duplicate.line),
122
+ message: format!("duplicate: \"{}\"", duplicate.value),
123
+ })
124
+ .collect(),
125
+
126
+ YerbaError::SchemaValidation(errors) => errors
127
+ .iter()
128
+ .map(|error| GitHubAnnotation {
129
+ level: "error ",
130
+ file: file.to_string(),
131
+ line: error.line,
132
+ message: error.to_string(),
133
+ })
134
+ .collect(),
135
+
136
+ YerbaError::DuplicateKey { key, duplicate_line, .. } => vec![GitHubAnnotation {
137
+ level: "error ",
138
+ file: file.to_string(),
139
+ line: Some(*duplicate_line),
140
+ message: format!("duplicate key: \"{}\"", key),
141
+ }],
142
+
143
+ _ => vec![GitHubAnnotation {
144
+ level: "error ",
145
+ file: file.to_string(),
146
+ line: None,
147
+ message: self.to_string(),
148
+ }],
149
+ }
150
+ }
151
+ }
data/rust/src/ffi.rs CHANGED
@@ -298,13 +298,22 @@ pub unsafe extern "C" fn yerba_document_get_value(document: *const Document, pat
298
298
 
299
299
  /// Caller must free with yerba_string_free.
300
300
  #[no_mangle]
301
- pub unsafe extern "C" fn yerba_document_get_values(document: *const Document, path: *const c_char) -> *mut c_char {
301
+ pub unsafe extern "C" fn yerba_document_selectors(document: *const Document) -> *mut c_char {
302
+ let document = &*document;
303
+
304
+ let selectors = document.selectors();
305
+ let json_string = serde_json::to_string(&selectors).unwrap_or_else(|_| "[]".to_string());
306
+
307
+ CString::new(json_string).unwrap_or_default().into_raw()
308
+ }
309
+
310
+ #[no_mangle]
311
+ pub unsafe extern "C" fn yerba_document_resolve_selectors(document: *const Document, path: *const c_char) -> *mut c_char {
302
312
  let document = &*document;
303
313
  let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
304
314
 
305
- let values = document.get_values(selector_string);
306
- let json_values: Vec<serde_json::Value> = values.iter().map(crate::json::yaml_to_json).collect();
307
- let json_string = serde_json::to_string(&json_values).unwrap_or_else(|_| "[]".to_string());
315
+ let selectors = document.resolve_selectors(selector_string);
316
+ let json_string = serde_json::to_string(&selectors).unwrap_or_else(|_| "[]".to_string());
308
317
 
309
318
  CString::new(json_string).unwrap_or_default().into_raw()
310
319
  }
@@ -357,6 +366,13 @@ pub unsafe extern "C" fn yerba_document_exists(document: *const Document, path:
357
366
  document.exists(selector_string)
358
367
  }
359
368
 
369
+ #[no_mangle]
370
+ pub unsafe extern "C" fn yerba_document_valid_selector(document: *const Document, path: *const c_char) -> bool {
371
+ let document = &*document;
372
+ let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
373
+ document.is_valid_selector(selector_string)
374
+ }
375
+
360
376
  #[no_mangle]
361
377
  pub unsafe extern "C" fn yerba_document_find(document: *const Document, path: *const c_char, condition: *const c_char, select: *const c_char) -> *mut c_char {
362
378
  let document = &*document;
@@ -404,13 +420,19 @@ pub unsafe extern "C" fn yerba_document_insert(
404
420
  document: *mut Document,
405
421
  path: *const c_char,
406
422
  value: *const c_char,
423
+ value_type: YerbaValueType,
407
424
  before: *const c_char,
408
425
  after: *const c_char,
409
426
  at: i64,
410
427
  ) -> YerbaResult {
411
428
  let document = &mut *document;
412
429
  let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
413
- let value_string = CStr::from_ptr(value).to_str().unwrap_or("");
430
+ let raw_value = CStr::from_ptr(value).to_str().unwrap_or("");
431
+
432
+ let value_string = match value_type {
433
+ YerbaValueType::String => crate::syntax::quote_if_needed(raw_value),
434
+ _ => raw_value.to_string(),
435
+ };
414
436
 
415
437
  let position = if at >= 0 {
416
438
  InsertPosition::At(at as usize)
@@ -424,7 +446,7 @@ pub unsafe extern "C" fn yerba_document_insert(
424
446
  InsertPosition::Last
425
447
  };
426
448
 
427
- match document.insert_into(selector_string, value_string, position) {
449
+ match document.insert_into(selector_string, &value_string, position) {
428
450
  Ok(()) => YerbaResult::ok(),
429
451
  Err(e) => YerbaResult::err(&e.to_string()),
430
452
  }
@@ -626,6 +648,44 @@ pub unsafe extern "C" fn yerba_document_blank_lines(document: *mut Document, pat
626
648
  }
627
649
  }
628
650
 
651
+ /// Caller must free with yerba_string_free.
652
+ #[no_mangle]
653
+ pub unsafe extern "C" fn yerba_document_validate_schema(document: *const Document, schema_json: *const c_char, selector: *const c_char) -> *mut c_char {
654
+ let document = &*document;
655
+ let schema_string = CStr::from_ptr(schema_json).to_str().unwrap_or("");
656
+ let selector_string = if selector.is_null() { None } else { CStr::from_ptr(selector).to_str().ok() };
657
+
658
+ let schema: serde_json::Value = match serde_json::from_str(schema_string) {
659
+ Ok(schema) => schema,
660
+ Err(e) => {
661
+ let error = serde_json::json!([{"message": format!("invalid schema: {}", e), "path": "", "line": null}]);
662
+ return CString::new(error.to_string()).unwrap_or_default().into_raw();
663
+ }
664
+ };
665
+
666
+ let errors = document.validate_schema(&schema, false, selector_string);
667
+
668
+ if errors.is_empty() {
669
+ return ptr::null_mut();
670
+ }
671
+
672
+ let json_errors: Vec<serde_json::Value> = errors
673
+ .iter()
674
+ .map(|error| {
675
+ serde_json::json!({
676
+ "message": error.message,
677
+ "path": error.path,
678
+ "line": error.line,
679
+ "item_label": error.item_label,
680
+ })
681
+ })
682
+ .collect();
683
+
684
+ CString::new(serde_json::to_string(&json_errors).unwrap_or_else(|_| "[]".to_string()))
685
+ .unwrap_or_default()
686
+ .into_raw()
687
+ }
688
+
629
689
  /// Caller must free with yerba_string_free.
630
690
  #[no_mangle]
631
691
  pub unsafe extern "C" fn yerba_yerbafile_find(directory: *const c_char) -> *mut c_char {
@@ -716,11 +776,31 @@ pub unsafe extern "C" fn yerba_get_result_free(result: YerbaGetResult) {
716
776
  pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
717
777
  let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
718
778
  let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
719
- let scalars = crate::glob_get(pattern, selector_string);
779
+ let nodes = crate::glob_get(pattern, selector_string);
720
780
 
721
- let results: Vec<serde_json::Value> = scalars
781
+ let results: Vec<serde_json::Value> = nodes
722
782
  .iter()
723
- .map(|scalar| serde_json::json!({"text": scalar.text, "type": detect_yaml_type(scalar) as u8}))
783
+ .map(|node| {
784
+ let mut value = serde_json::json!({
785
+ "node_type": node.node_type,
786
+ "selector": node.selector,
787
+ "line": node.line,
788
+ });
789
+
790
+ if let Some(file_path) = &node.file_path {
791
+ value["file_path"] = serde_json::json!(file_path);
792
+ }
793
+
794
+ if let Some(text) = &node.text {
795
+ value["text"] = serde_json::json!(text);
796
+ }
797
+
798
+ if let Some(vt) = node.value_type {
799
+ value["type"] = serde_json::json!(vt as u8);
800
+ }
801
+
802
+ value
803
+ })
724
804
  .collect();
725
805
 
726
806
  let length = results.len();
data/rust/src/json.rs CHANGED
@@ -1,11 +1,11 @@
1
1
  use crate::selector::{Selector, SelectorSegment};
2
2
 
3
- pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
3
+ pub fn yaml_to_json(value: &yaml_serde::Value) -> serde_json::Value {
4
4
  match value {
5
- serde_yaml::Value::Null => serde_json::Value::Null,
6
- serde_yaml::Value::Bool(boolean) => serde_json::Value::Bool(*boolean),
5
+ yaml_serde::Value::Null => serde_json::Value::Null,
6
+ yaml_serde::Value::Bool(boolean) => serde_json::Value::Bool(*boolean),
7
7
 
8
- serde_yaml::Value::Number(number) => {
8
+ yaml_serde::Value::Number(number) => {
9
9
  if let Some(integer) = number.as_i64() {
10
10
  serde_json::Value::Number(integer.into())
11
11
  } else if let Some(float) = number.as_f64() {
@@ -15,16 +15,16 @@ pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
15
15
  }
16
16
  }
17
17
 
18
- serde_yaml::Value::String(string) => serde_json::Value::String(string.clone()),
18
+ yaml_serde::Value::String(string) => serde_json::Value::String(string.clone()),
19
19
 
20
- serde_yaml::Value::Sequence(sequence) => serde_json::Value::Array(sequence.iter().map(yaml_to_json).collect()),
20
+ yaml_serde::Value::Sequence(sequence) => serde_json::Value::Array(sequence.iter().map(yaml_to_json).collect()),
21
21
 
22
- serde_yaml::Value::Mapping(mapping) => {
22
+ yaml_serde::Value::Mapping(mapping) => {
23
23
  let mut map = serde_json::Map::new();
24
24
 
25
25
  for (key, yaml_value) in mapping {
26
26
  let json_key = match key {
27
- serde_yaml::Value::String(string) => string.clone(),
27
+ yaml_serde::Value::String(string) => string.clone(),
28
28
  _ => format!("{:?}", key),
29
29
  };
30
30
 
@@ -34,19 +34,19 @@ pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
34
34
  serde_json::Value::Object(map)
35
35
  }
36
36
 
37
- serde_yaml::Value::Tagged(tagged) => yaml_to_json(&tagged.value),
37
+ yaml_serde::Value::Tagged(tagged) => yaml_to_json(&tagged.value),
38
38
  }
39
39
  }
40
40
 
41
- pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_json::Value {
41
+ pub fn resolve_select_field(value: &yaml_serde::Value, field: &str) -> serde_json::Value {
42
42
  let parsed = Selector::parse(field);
43
43
  let segments = parsed.segments();
44
44
 
45
45
  if segments.len() == 1 {
46
46
  if let SelectorSegment::Key(key) = &segments[0] {
47
- if let serde_yaml::Value::Mapping(map) = value {
47
+ if let yaml_serde::Value::Mapping(map) = value {
48
48
  for (map_key, yaml_value) in map {
49
- if let serde_yaml::Value::String(key_string) = map_key {
49
+ if let yaml_serde::Value::String(key_string) = map_key {
50
50
  if key_string == key {
51
51
  return yaml_to_json(yaml_value);
52
52
  }
@@ -66,13 +66,13 @@ pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_jso
66
66
  for current in &current_values {
67
67
  match segment {
68
68
  SelectorSegment::AllItems => {
69
- if let serde_yaml::Value::Sequence(sequence) = current {
69
+ if let yaml_serde::Value::Sequence(sequence) = current {
70
70
  next_values.extend(sequence.iter().cloned());
71
71
  }
72
72
  }
73
73
 
74
74
  SelectorSegment::Index(index) => {
75
- if let serde_yaml::Value::Sequence(sequence) = current {
75
+ if let yaml_serde::Value::Sequence(sequence) = current {
76
76
  if let Some(item) = sequence.get(*index) {
77
77
  next_values.push(item.clone());
78
78
  }
@@ -80,9 +80,9 @@ pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_jso
80
80
  }
81
81
 
82
82
  SelectorSegment::Key(key) => {
83
- if let serde_yaml::Value::Mapping(map) = current {
83
+ if let yaml_serde::Value::Mapping(map) = current {
84
84
  for (map_key, yaml_value) in map {
85
- if let serde_yaml::Value::String(key_string) = map_key {
85
+ if let yaml_serde::Value::String(key_string) = map_key {
86
86
  if key_string == key {
87
87
  next_values.push(yaml_value.clone());
88
88
  }
data/rust/src/lib.rs CHANGED
@@ -1,15 +1,16 @@
1
1
  pub mod didyoumean;
2
2
  mod document;
3
- mod error;
3
+ pub mod error;
4
4
  pub mod ffi;
5
5
  pub mod json;
6
6
  mod quote_style;
7
+ pub mod schema;
7
8
  pub mod selector;
8
9
  mod syntax;
9
10
  mod yaml_writer;
10
11
  pub mod yerbafile;
11
12
 
12
- pub use document::{collect_selectors, Document, InsertPosition, Location, NodeInfo, NodeType, SortField};
13
+ pub use document::{collect_selectors, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField};
13
14
  pub use error::YerbaError;
14
15
  pub use quote_style::{KeyStyle, QuoteStyle};
15
16
  pub use selector::Selector;
@@ -29,11 +30,9 @@ pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Document, YerbaEr
29
30
  Document::parse_file(path)
30
31
  }
31
32
 
32
- pub fn glob_get(pattern: &str, selector: &str) -> Vec<ScalarValue> {
33
+ pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
33
34
  use rayon::prelude::*;
34
35
 
35
- let parsed_selector = Selector::parse(selector);
36
-
37
36
  let files = match glob::glob(pattern) {
38
37
  Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
39
38
  Err(_) => return vec![],
@@ -45,11 +44,7 @@ pub fn glob_get(pattern: &str, selector: &str) -> Vec<ScalarValue> {
45
44
  let mut results = Vec::new();
46
45
 
47
46
  if let Ok(document) = Document::parse_file(file) {
48
- if parsed_selector.has_wildcard() {
49
- results.extend(document.get_all_typed(selector));
50
- } else if let Some(scalar) = document.get_typed(selector) {
51
- results.push(scalar);
52
- }
47
+ results.extend(document.get_all_located(selector));
53
48
  }
54
49
 
55
50
  results
@@ -93,10 +88,10 @@ pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select:
93
88
  }
94
89
  }
95
90
  None => {
96
- if let serde_yaml::Value::Mapping(map) = value {
91
+ if let yaml_serde::Value::Mapping(map) = value {
97
92
  for (key, yaml_value) in map {
98
93
  let json_key = match key {
99
- serde_yaml::Value::String(string) => string.clone(),
94
+ yaml_serde::Value::String(string) => string.clone(),
100
95
  _ => format!("{:?}", key),
101
96
  };
102
97
 
data/rust/src/main.rs CHANGED
@@ -48,7 +48,9 @@ static HELP: LazyLock<String> = LazyLock::new(|| {
48
48
  yerba sort-keys config.yml "database" "id,host,port,name"
49
49
  yerba quote-style "data/**/*.yml" --values double
50
50
  yerba sort videos.yml "[]" --by ".id" --order "talk-3,talk-1,talk-2"
51
+ yerba schema "data/**/*.yml" --schema schema.json
51
52
  yerba selectors videos.yml
53
+ yerba location videos.yml "[0].title"
52
54
  "#})
53
55
  });
54
56