yerba 0.1.2 → 0.2.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +492 -15
  3. data/ext/yerba/extconf.rb +87 -30
  4. data/ext/yerba/include/yerba.h +168 -0
  5. data/ext/yerba/yerba.c +752 -0
  6. data/lib/yerba/collection.rb +31 -0
  7. data/lib/yerba/document.rb +59 -0
  8. data/lib/yerba/formatting.rb +18 -0
  9. data/lib/yerba/location.rb +5 -0
  10. data/lib/yerba/map.rb +166 -0
  11. data/lib/yerba/scalar.rb +85 -0
  12. data/lib/yerba/sequence.rb +182 -0
  13. data/lib/yerba/version.rb +1 -1
  14. data/lib/yerba.rb +30 -4
  15. data/rust/Cargo.lock +378 -2
  16. data/rust/Cargo.toml +5 -1
  17. data/rust/build.rs +11 -0
  18. data/rust/cbindgen.toml +27 -0
  19. data/rust/src/commands/apply.rs +5 -0
  20. data/rust/src/commands/blank_lines.rs +58 -0
  21. data/rust/src/commands/check.rs +5 -0
  22. data/rust/src/commands/delete.rs +35 -0
  23. data/rust/src/commands/get.rs +194 -0
  24. data/rust/src/commands/init.rs +89 -0
  25. data/rust/src/commands/insert.rs +106 -0
  26. data/rust/src/commands/mate.rs +55 -0
  27. data/rust/src/commands/mod.rs +349 -0
  28. data/rust/src/commands/move_item.rs +54 -0
  29. data/rust/src/commands/move_key.rs +87 -0
  30. data/rust/src/commands/quote_style.rs +62 -0
  31. data/rust/src/commands/remove.rs +35 -0
  32. data/rust/src/commands/rename.rs +37 -0
  33. data/rust/src/commands/set.rs +59 -0
  34. data/rust/src/commands/sort.rs +52 -0
  35. data/rust/src/commands/sort_keys.rs +62 -0
  36. data/rust/src/commands/version.rs +8 -0
  37. data/rust/src/document.rs +764 -333
  38. data/rust/src/error.rs +0 -5
  39. data/rust/src/ffi.rs +991 -0
  40. data/rust/src/json.rs +49 -90
  41. data/rust/src/lib.rs +9 -2
  42. data/rust/src/main.rs +55 -843
  43. data/rust/src/selector.rs +241 -0
  44. data/rust/src/syntax.rs +97 -21
  45. data/rust/src/yaml_writer.rs +89 -0
  46. data/rust/src/yerbafile.rs +11 -126
  47. data/yerba.gemspec +4 -0
  48. metadata +33 -1
data/rust/src/document.rs CHANGED
@@ -11,18 +11,11 @@ use crate::error::YerbaError;
11
11
  use crate::QuoteStyle;
12
12
 
13
13
  use crate::syntax::{
14
- extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key, is_yaml_non_string,
15
- preceding_whitespace_indent, preceding_whitespace_token, removal_range, unescape_double_quoted,
16
- unescape_single_quoted,
14
+ extract_scalar, extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key,
15
+ is_yaml_non_string, preceding_whitespace_indent, preceding_whitespace_token, removal_range, unescape_double_quoted,
16
+ unescape_single_quoted, ScalarValue,
17
17
  };
18
18
 
19
- #[derive(Debug)]
20
- pub struct FindResult {
21
- pub text: String,
22
- pub line: usize,
23
- pub end_line: usize,
24
- }
25
-
26
19
  #[derive(Debug, Clone)]
27
20
  pub struct SortField {
28
21
  pub path: String,
@@ -66,6 +59,8 @@ pub enum InsertPosition {
66
59
  Last,
67
60
  Before(String),
68
61
  After(String),
62
+ BeforeCondition(String),
63
+ AfterCondition(String),
69
64
  FromSortOrder(Vec<String>),
70
65
  }
71
66
 
@@ -98,56 +93,79 @@ impl Document {
98
93
  return self.get_all(dot_path).into_iter().next();
99
94
  }
100
95
 
101
- let keys: Vec<&str> = dot_path.split('.').collect();
102
- let current_node = self.navigate_to_path(&keys).ok()?;
96
+ let current_node = self.navigate(dot_path).ok()?;
103
97
 
104
98
  extract_scalar_text(&current_node)
105
99
  }
106
100
 
107
101
  pub fn get_all(&self, dot_path: &str) -> Vec<String> {
108
102
  self
109
- .navigate_to_many(dot_path)
103
+ .navigate_all(dot_path)
110
104
  .iter()
111
105
  .filter_map(extract_scalar_text)
112
106
  .collect()
113
107
  }
114
108
 
115
- pub fn find_items(&self, dot_path: &str, condition: &str) -> Vec<FindResult> {
116
- let source = self.root.text().to_string();
117
- let nodes = self.navigate_to_many(dot_path);
109
+ pub fn get_typed(&self, dot_path: &str) -> Option<ScalarValue> {
110
+ if crate::selector::Selector::parse(dot_path).has_wildcard() {
111
+ return self.get_all_typed(dot_path).into_iter().next();
112
+ }
113
+
114
+ let current_node = self.navigate(dot_path).ok()?;
115
+
116
+ if current_node
117
+ .descendants()
118
+ .any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
119
+ {
120
+ return None;
121
+ }
122
+
123
+ extract_scalar(&current_node)
124
+ }
118
125
 
119
- nodes
126
+ pub fn get_all_typed(&self, dot_path: &str) -> Vec<ScalarValue> {
127
+ self
128
+ .navigate_all(dot_path)
120
129
  .iter()
121
- .filter(|node| self.evaluate_condition_on_node(node, condition))
122
- .map(|node| {
123
- let start_offset: usize = node.text_range().start().into();
124
- let end_offset: usize = node.text_range().end().into();
125
-
126
- FindResult {
127
- text: node.text().to_string(),
128
- line: byte_offset_to_line(&source, start_offset),
129
- end_line: byte_offset_to_line(&source, end_offset),
130
- }
130
+ .filter(|node| {
131
+ !node
132
+ .descendants()
133
+ .any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
131
134
  })
135
+ .filter_map(extract_scalar)
132
136
  .collect()
133
137
  }
134
138
 
135
- pub fn find_all(&self, dot_path: &str) -> Vec<FindResult> {
136
- let source = self.root.text().to_string();
139
+ pub fn get_value(&self, dot_path: &str) -> Option<serde_yaml::Value> {
140
+ if dot_path.is_empty() {
141
+ return Some(node_to_yaml_value(&self.root));
142
+ }
143
+
144
+ let nodes = self.navigate_all(dot_path);
145
+
146
+ if nodes.is_empty() {
147
+ return None;
148
+ }
149
+
150
+ if nodes.len() == 1 {
151
+ return Some(node_to_yaml_value(&nodes[0]));
152
+ }
153
+
154
+ let values: Vec<serde_yaml::Value> = nodes.iter().map(node_to_yaml_value).collect();
137
155
 
156
+ Some(serde_yaml::Value::Sequence(values))
157
+ }
158
+
159
+ pub fn get_values(&self, dot_path: &str) -> Vec<serde_yaml::Value> {
160
+ self.navigate_all(dot_path).iter().map(node_to_yaml_value).collect()
161
+ }
162
+
163
+ pub fn filter(&self, dot_path: &str, condition: &str) -> Vec<serde_yaml::Value> {
138
164
  self
139
- .navigate_to_many(dot_path)
165
+ .navigate_all(dot_path)
140
166
  .iter()
141
- .map(|node| {
142
- let start_offset: usize = node.text_range().start().into();
143
- let end_offset: usize = node.text_range().end().into();
144
-
145
- FindResult {
146
- text: node.text().to_string(),
147
- line: byte_offset_to_line(&source, start_offset),
148
- end_line: byte_offset_to_line(&source, end_offset),
149
- }
150
- })
167
+ .filter(|node| self.evaluate_condition_on_node(node, condition))
168
+ .map(node_to_yaml_value)
151
169
  .collect()
152
170
  }
153
171
 
@@ -159,9 +177,13 @@ impl Document {
159
177
  None => return false,
160
178
  };
161
179
 
162
- let left_path = left.strip_prefix('.').unwrap_or(&left);
163
- let target_nodes = navigate_from_node(node, left_path);
180
+ let path = crate::selector::Selector::parse(&left);
181
+
182
+ if !path.is_relative() {
183
+ return false;
184
+ }
164
185
 
186
+ let target_nodes = navigate_from_node(node, &path.to_selector_string());
165
187
  let values: Vec<String> = target_nodes.iter().filter_map(extract_scalar_text).collect();
166
188
 
167
189
  match operator {
@@ -207,7 +229,7 @@ impl Document {
207
229
 
208
230
  pub fn exists(&self, dot_path: &str) -> bool {
209
231
  if dot_path.contains('[') {
210
- return !self.navigate_to_many(dot_path).is_empty();
232
+ return !self.navigate_all(dot_path).is_empty();
211
233
  }
212
234
 
213
235
  self.get(dot_path).is_some()
@@ -221,17 +243,21 @@ impl Document {
221
243
  None => return false,
222
244
  };
223
245
 
224
- let full_path = if let Some(relative_key) = left.strip_prefix('.') {
246
+ let path = crate::selector::Selector::parse(&left);
247
+
248
+ let full_path = if path.is_relative() {
249
+ let path_string = path.to_selector_string();
250
+
225
251
  if parent_path.is_empty() {
226
- relative_key.to_string()
252
+ path_string
227
253
  } else {
228
- format!("{}.{}", parent_path, relative_key)
254
+ format!("{}.{}", parent_path, path_string)
229
255
  }
230
256
  } else {
231
- left.to_string()
257
+ path.to_selector_string()
232
258
  };
233
259
 
234
- let has_brackets = full_path.contains('[');
260
+ let has_brackets = crate::selector::Selector::parse(&full_path).has_brackets();
235
261
 
236
262
  match operator {
237
263
  "==" => {
@@ -291,8 +317,7 @@ impl Document {
291
317
  }
292
318
 
293
319
  pub fn get_sequence_values(&self, dot_path: &str) -> Vec<String> {
294
- let keys: Vec<&str> = dot_path.split('.').collect();
295
- let current_node = match self.navigate_to_path(&keys) {
320
+ let current_node = match self.navigate(dot_path) {
296
321
  Ok(node) => node,
297
322
  Err(_) => return Vec::new(),
298
323
  };
@@ -309,8 +334,24 @@ impl Document {
309
334
  }
310
335
 
311
336
  pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
312
- let keys: Vec<&str> = dot_path.split('.').collect();
313
- let current_node = self.navigate_to_path(&keys)?;
337
+ let current_node = self.navigate(dot_path)?;
338
+
339
+ if let Some(block_scalar) = current_node
340
+ .descendants()
341
+ .find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR)
342
+ {
343
+ let new_text = if value.is_empty() {
344
+ "\"\"".to_string()
345
+ } else if value.contains('\n') {
346
+ format!("|-\n {}", value.replace('\n', "\n "))
347
+ } else {
348
+ format!("\"{}\"", value.replace('"', "\\\""))
349
+ };
350
+
351
+ let range = block_scalar.text_range();
352
+
353
+ return self.apply_edit(range, &new_text);
354
+ }
314
355
 
315
356
  let scalar_token =
316
357
  find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
@@ -320,14 +361,64 @@ impl Document {
320
361
  self.replace_token(&scalar_token, &new_text)
321
362
  }
322
363
 
364
+ pub fn set_scalar_style(&mut self, dot_path: &str, style: &QuoteStyle) -> Result<(), YerbaError> {
365
+ let current_node = self.navigate(dot_path)?;
366
+ let scalar_token =
367
+ find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
368
+
369
+ let current_kind = scalar_token.kind();
370
+ let target_kind = style.to_syntax_kind();
371
+
372
+ if current_kind == target_kind {
373
+ return Ok(());
374
+ }
375
+
376
+ let raw_value = match current_kind {
377
+ SyntaxKind::DOUBLE_QUOTED_SCALAR => {
378
+ let text = scalar_token.text();
379
+ unescape_double_quoted(&text[1..text.len() - 1])
380
+ }
381
+
382
+ SyntaxKind::SINGLE_QUOTED_SCALAR => {
383
+ let text = scalar_token.text();
384
+ unescape_single_quoted(&text[1..text.len() - 1])
385
+ }
386
+
387
+ SyntaxKind::PLAIN_SCALAR => scalar_token.text().to_string(),
388
+
389
+ _ => return Ok(()),
390
+ };
391
+
392
+ let new_text = format_scalar_value(&raw_value, target_kind);
393
+
394
+ self.replace_token(&scalar_token, &new_text)
395
+ }
396
+
397
+ pub fn set_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
398
+ let current_node = self.navigate(dot_path)?;
399
+
400
+ if let Some(block_scalar) = current_node
401
+ .descendants()
402
+ .find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR)
403
+ {
404
+ let range = block_scalar.text_range();
405
+ return self.apply_edit(range, value);
406
+ }
407
+
408
+ let scalar_token =
409
+ find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
410
+
411
+ self.replace_token(&scalar_token, value)
412
+ }
413
+
323
414
  pub fn append(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
324
415
  self.insert_into(dot_path, value, InsertPosition::Last)
325
416
  }
326
417
 
327
418
  pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
328
- let keys: Vec<&str> = dot_path.split('.').collect();
419
+ Self::validate_path(dot_path)?;
329
420
 
330
- if let Ok(current_node) = self.navigate_to_path(&keys) {
421
+ if let Ok(current_node) = self.navigate(dot_path) {
331
422
  if current_node.descendants().find_map(BlockSeq::cast).is_some() {
332
423
  return self.insert_sequence_item(dot_path, value, position);
333
424
  }
@@ -339,8 +430,7 @@ impl Document {
339
430
  }
340
431
 
341
432
  fn insert_sequence_item(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
342
- let keys: Vec<&str> = dot_path.split('.').collect();
343
- let current_node = self.navigate_to_path(&keys)?;
433
+ let current_node = self.navigate(dot_path)?;
344
434
 
345
435
  let sequence = current_node
346
436
  .descendants()
@@ -359,7 +449,37 @@ impl Document {
359
449
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
360
450
  .unwrap_or_default();
361
451
 
362
- let new_item = format!("- {}", value);
452
+ let new_item = if value.contains('\n') {
453
+ let item_indent = format!("{} ", indent);
454
+ let lines: Vec<&str> = value.split('\n').collect();
455
+
456
+ let min_indent = lines
457
+ .iter()
458
+ .skip(1)
459
+ .filter(|line| !line.trim().is_empty())
460
+ .map(|line| line.len() - line.trim_start().len())
461
+ .min()
462
+ .unwrap_or(0);
463
+
464
+ let indented: Vec<String> = lines
465
+ .iter()
466
+ .enumerate()
467
+ .map(|(index, line)| {
468
+ if index == 0 {
469
+ line.to_string()
470
+ } else if line.trim().is_empty() {
471
+ String::new()
472
+ } else {
473
+ let relative = &line[min_indent..];
474
+ format!("{}{}", item_indent, relative)
475
+ }
476
+ })
477
+ .collect();
478
+
479
+ format!("- {}", indented.join("\n"))
480
+ } else {
481
+ format!("- {}", value)
482
+ };
363
483
 
364
484
  match position {
365
485
  InsertPosition::Last => {
@@ -421,6 +541,30 @@ impl Document {
421
541
  self.insert_after_node(target_entry.syntax(), &new_text)
422
542
  }
423
543
 
544
+ InsertPosition::BeforeCondition(condition) => {
545
+ let target_entry = entries
546
+ .iter()
547
+ .find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
548
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, condition)))?;
549
+
550
+ let target_range = target_entry.syntax().text_range();
551
+ let replacement = format!("{}\n{}", new_item, indent);
552
+ let insert_range = TextRange::new(target_range.start(), target_range.start());
553
+
554
+ self.apply_edit(insert_range, &replacement)
555
+ }
556
+
557
+ InsertPosition::AfterCondition(condition) => {
558
+ let target_entry = entries
559
+ .iter()
560
+ .find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
561
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, condition)))?;
562
+
563
+ let new_text = format!("\n{}{}", indent, new_item);
564
+
565
+ self.insert_after_node(target_entry.syntax(), &new_text)
566
+ }
567
+
424
568
  InsertPosition::FromSortOrder(_) => {
425
569
  let last_entry = entries.last().unwrap();
426
570
  let new_text = format!("\n{}{}", indent, new_item);
@@ -437,8 +581,7 @@ impl Document {
437
581
  value: &str,
438
582
  position: InsertPosition,
439
583
  ) -> Result<(), YerbaError> {
440
- let keys: Vec<&str> = dot_path.split('.').collect();
441
- let current_node = self.navigate_to_path(&keys)?;
584
+ let current_node = self.navigate(dot_path)?;
442
585
 
443
586
  let map = current_node
444
587
  .descendants()
@@ -513,6 +656,10 @@ impl Document {
513
656
  self.insert_after_node(target_entry.syntax(), &new_text)
514
657
  }
515
658
 
659
+ InsertPosition::BeforeCondition(_) | InsertPosition::AfterCondition(_) => {
660
+ self.insert_map_key(dot_path, key, value, InsertPosition::Last)
661
+ }
662
+
516
663
  InsertPosition::FromSortOrder(order) => {
517
664
  let new_key_position = order.iter().position(|ordered_key| ordered_key == key);
518
665
 
@@ -542,6 +689,9 @@ impl Document {
542
689
  }
543
690
 
544
691
  pub fn rename(&mut self, source_path: &str, destination_path: &str) -> Result<(), YerbaError> {
692
+ Self::validate_path(source_path)?;
693
+ Self::validate_path(destination_path)?;
694
+
545
695
  let source_parent = source_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
546
696
 
547
697
  let destination_parent = destination_path
@@ -555,9 +705,8 @@ impl Document {
555
705
  .unwrap_or(destination_path);
556
706
 
557
707
  if source_parent == destination_parent {
558
- let keys: Vec<&str> = source_path.split('.').collect();
559
- let parent_node = self.navigate_to_path(&keys[..keys.len() - 1])?;
560
- let source_key = keys.last().unwrap();
708
+ let (parent_path, source_key) = source_path.rsplit_once('.').unwrap_or(("", source_path));
709
+ let parent_node = self.navigate(parent_path)?;
561
710
 
562
711
  let map = parent_node
563
712
  .descendants()
@@ -588,9 +737,10 @@ impl Document {
588
737
  }
589
738
 
590
739
  pub fn delete(&mut self, dot_path: &str) -> Result<(), YerbaError> {
591
- let keys: Vec<&str> = dot_path.split('.').collect();
592
- let parent_node = self.navigate_to_path(&keys[..keys.len() - 1])?;
593
- let last_key = keys.last().unwrap();
740
+ Self::validate_path(dot_path)?;
741
+
742
+ let (parent_path, last_key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
743
+ let parent_node = self.navigate(parent_path)?;
594
744
 
595
745
  let map = parent_node
596
746
  .descendants()
@@ -603,8 +753,7 @@ impl Document {
603
753
  }
604
754
 
605
755
  pub fn remove(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
606
- let keys: Vec<&str> = dot_path.split('.').collect();
607
- let current_node = self.navigate_to_path(&keys)?;
756
+ let current_node = self.navigate(dot_path)?;
608
757
 
609
758
  let sequence = current_node
610
759
  .descendants()
@@ -625,13 +774,31 @@ impl Document {
625
774
  self.remove_node(target_entry.syntax())
626
775
  }
627
776
 
777
+ pub fn remove_at(&mut self, dot_path: &str, index: usize) -> Result<(), YerbaError> {
778
+ Self::validate_path(dot_path)?;
779
+
780
+ let current_node = self.navigate(dot_path)?;
781
+
782
+ let sequence = current_node
783
+ .descendants()
784
+ .find_map(BlockSeq::cast)
785
+ .ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
786
+
787
+ let entries: Vec<_> = sequence.entries().collect();
788
+
789
+ if index >= entries.len() {
790
+ return Err(YerbaError::IndexOutOfBounds(index, entries.len()));
791
+ }
792
+
793
+ self.remove_node(entries[index].syntax())
794
+ }
795
+
628
796
  pub fn move_item(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
629
797
  if from == to {
630
798
  return Ok(());
631
799
  }
632
800
 
633
- let keys: Vec<&str> = dot_path.split('.').collect();
634
- let current_node = self.navigate_to_path(&keys)?;
801
+ let current_node = self.navigate(dot_path)?;
635
802
 
636
803
  let sequence = current_node
637
804
  .descendants()
@@ -640,14 +807,7 @@ impl Document {
640
807
 
641
808
  let entries: Vec<_> = sequence.entries().collect();
642
809
 
643
- self.reorder_entries(
644
- &entries,
645
- from,
646
- to,
647
- |entry| entry.syntax().text().to_string(),
648
- |entry| preceding_whitespace_indent(entry.syntax()),
649
- sequence.syntax().text_range(),
650
- )
810
+ self.reorder_entries(sequence.syntax(), &entries, from, to)
651
811
  }
652
812
 
653
813
  pub fn move_key(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
@@ -655,8 +815,7 @@ impl Document {
655
815
  return Ok(());
656
816
  }
657
817
 
658
- let keys: Vec<&str> = dot_path.split('.').collect();
659
- let current_node = self.navigate_to_path(&keys)?;
818
+ let current_node = self.navigate(dot_path)?;
660
819
 
661
820
  let map = current_node
662
821
  .descendants()
@@ -665,19 +824,11 @@ impl Document {
665
824
 
666
825
  let entries: Vec<_> = map.entries().collect();
667
826
 
668
- self.reorder_entries(
669
- &entries,
670
- from,
671
- to,
672
- |entry| entry.syntax().text().to_string(),
673
- |entry| preceding_whitespace_indent(entry.syntax()),
674
- map.syntax().text_range(),
675
- )
827
+ self.reorder_entries(map.syntax(), &entries, from, to)
676
828
  }
677
829
 
678
830
  pub fn resolve_key_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
679
- let keys: Vec<&str> = dot_path.split('.').collect();
680
- let current_node = self.navigate_to_path(&keys)?;
831
+ let current_node = self.navigate(dot_path)?;
681
832
 
682
833
  let map = current_node
683
834
  .descendants()
@@ -709,8 +860,7 @@ impl Document {
709
860
  }
710
861
 
711
862
  pub fn resolve_sequence_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
712
- let keys: Vec<&str> = dot_path.split('.').collect();
713
- let current_node = self.navigate_to_path(&keys)?;
863
+ let current_node = self.navigate(dot_path)?;
714
864
 
715
865
  let sequence = current_node
716
866
  .descendants()
@@ -727,6 +877,15 @@ impl Document {
727
877
  return Ok(index);
728
878
  }
729
879
 
880
+ if crate::selector::Selector::parse(reference).is_relative() {
881
+ return sequence
882
+ .entries()
883
+ .enumerate()
884
+ .find(|(_index, entry)| self.evaluate_condition_on_node(entry.syntax(), reference))
885
+ .map(|(index, _entry)| index)
886
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, reference)));
887
+ }
888
+
730
889
  sequence
731
890
  .entries()
732
891
  .enumerate()
@@ -752,8 +911,7 @@ impl Document {
752
911
  return self.validate_each_sort_keys(seq_path, key_order);
753
912
  }
754
913
 
755
- let keys: Vec<&str> = dot_path.split('.').collect();
756
- let current_node = self.navigate_to_path(&keys)?;
914
+ let current_node = self.navigate(dot_path)?;
757
915
 
758
916
  let map = current_node
759
917
  .descendants()
@@ -784,8 +942,7 @@ impl Document {
784
942
  return self.sort_each_keys(seq_path, key_order);
785
943
  }
786
944
 
787
- let keys: Vec<&str> = dot_path.split('.').collect();
788
- let current_node = self.navigate_to_path(&keys)?;
945
+ let current_node = self.navigate(dot_path)?;
789
946
 
790
947
  let map = current_node
791
948
  .descendants()
@@ -798,21 +955,23 @@ impl Document {
798
955
  return Ok(());
799
956
  }
800
957
 
801
- let entry_data: Vec<(String, String)> = entries
958
+ let (groups, range) = collect_groups_with_range(map.syntax());
959
+
960
+ let mut keyed: Vec<(String, EntryGroup)> = entries
802
961
  .iter()
803
- .map(|entry| {
962
+ .zip(groups)
963
+ .map(|(entry, group)| {
804
964
  let key_name = entry
805
965
  .key()
806
966
  .and_then(|key_node| extract_scalar_text(key_node.syntax()))
807
967
  .unwrap_or_default();
808
- let text = entry.syntax().text().to_string();
809
- (key_name, text)
968
+ (key_name, group)
810
969
  })
811
970
  .collect();
812
971
 
813
- let mut sorted = entry_data.clone();
972
+ let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
814
973
 
815
- sorted.sort_by(|(key_a, _), (key_b, _)| {
974
+ keyed.sort_by(|(key_a, _), (key_b, _)| {
816
975
  let position_a = key_order.iter().position(|&key| key == key_a);
817
976
  let position_b = key_order.iter().position(|&key| key == key_b);
818
977
 
@@ -821,16 +980,17 @@ impl Document {
821
980
  (Some(_), None) => std::cmp::Ordering::Less,
822
981
  (None, Some(_)) => std::cmp::Ordering::Greater,
823
982
  (None, None) => {
824
- let original_a = entry_data.iter().position(|(key, _)| key == key_a).unwrap();
825
- let original_b = entry_data.iter().position(|(key, _)| key == key_b).unwrap();
983
+ let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
984
+ let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
826
985
  original_a.cmp(&original_b)
827
986
  }
828
987
  }
829
988
  });
830
989
 
831
- if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
832
- == entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
833
- {
990
+ let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
991
+ let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
992
+
993
+ if sorted_keys == orig_refs {
834
994
  return Ok(());
835
995
  }
836
996
 
@@ -839,15 +999,14 @@ impl Document {
839
999
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
840
1000
  .unwrap_or_default();
841
1001
 
842
- let map_text = rebuild_entries(sorted.iter().map(|(_key, text)| text.as_str()), &indent);
843
- let map_range = map.syntax().text_range();
1002
+ let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
1003
+ let map_text = rebuild_from_groups(&sorted_groups, &indent);
844
1004
 
845
- self.apply_edit(map_range, &map_text)
1005
+ self.apply_edit(range, &map_text)
846
1006
  }
847
1007
 
848
1008
  pub fn sort_each_keys(&mut self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
849
- let keys: Vec<&str> = dot_path.split('.').collect();
850
- let current_node = self.navigate_to_path(&keys)?;
1009
+ let current_node = self.navigate(dot_path)?;
851
1010
 
852
1011
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
853
1012
  Some(sequence) => sequence,
@@ -870,21 +1029,23 @@ impl Document {
870
1029
  continue;
871
1030
  }
872
1031
 
873
- let entry_data: Vec<(String, String)> = entries
1032
+ let (groups, group_range) = collect_groups_with_range(map.syntax());
1033
+
1034
+ let mut keyed: Vec<(String, EntryGroup)> = entries
874
1035
  .iter()
875
- .map(|entry| {
1036
+ .zip(groups)
1037
+ .map(|(entry, group)| {
876
1038
  let key_name = entry
877
1039
  .key()
878
1040
  .and_then(|key_node| extract_scalar_text(key_node.syntax()))
879
1041
  .unwrap_or_default();
880
- let text = entry.syntax().text().to_string();
881
- (key_name, text)
1042
+ (key_name, group)
882
1043
  })
883
1044
  .collect();
884
1045
 
885
- let mut sorted = entry_data.clone();
1046
+ let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
886
1047
 
887
- sorted.sort_by(|(key_a, _), (key_b, _)| {
1048
+ keyed.sort_by(|(key_a, _), (key_b, _)| {
888
1049
  let position_a = key_order.iter().position(|&key| key == key_a);
889
1050
  let position_b = key_order.iter().position(|&key| key == key_b);
890
1051
 
@@ -894,17 +1055,18 @@ impl Document {
894
1055
  (None, Some(_)) => std::cmp::Ordering::Greater,
895
1056
 
896
1057
  (None, None) => {
897
- let original_a = entry_data.iter().position(|(key, _)| key == key_a).unwrap();
898
- let original_b = entry_data.iter().position(|(key, _)| key == key_b).unwrap();
1058
+ let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
1059
+ let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
899
1060
 
900
1061
  original_a.cmp(&original_b)
901
1062
  }
902
1063
  }
903
1064
  });
904
1065
 
905
- if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
906
- == entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
907
- {
1066
+ let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
1067
+ let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
1068
+
1069
+ if sorted_keys == orig_refs {
908
1070
  continue;
909
1071
  }
910
1072
 
@@ -913,9 +1075,9 @@ impl Document {
913
1075
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
914
1076
  .unwrap_or_default();
915
1077
 
916
- let map_text = rebuild_entries(sorted.iter().map(|(_key, text)| text.as_str()), &indent);
917
-
918
- edits.push((map.syntax().text_range(), map_text));
1078
+ let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
1079
+ let map_text = rebuild_from_groups(&sorted_groups, &indent);
1080
+ edits.push((group_range, map_text));
919
1081
  }
920
1082
 
921
1083
  if edits.is_empty() {
@@ -942,8 +1104,7 @@ impl Document {
942
1104
  }
943
1105
 
944
1106
  pub fn validate_each_sort_keys(&self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
945
- let keys: Vec<&str> = dot_path.split('.').collect();
946
- let current_node = self.navigate_to_path(&keys)?;
1107
+ let current_node = self.navigate(dot_path)?;
947
1108
 
948
1109
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
949
1110
  Some(sequence) => sequence,
@@ -984,8 +1145,7 @@ impl Document {
984
1145
  return self.sort_each_items(dot_path, sort_fields, case_sensitive);
985
1146
  }
986
1147
 
987
- let keys: Vec<&str> = dot_path.split('.').collect();
988
- let current_node = self.navigate_to_path(&keys)?;
1148
+ let current_node = self.navigate(dot_path)?;
989
1149
 
990
1150
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
991
1151
  Some(sequence) => sequence,
@@ -998,9 +1158,12 @@ impl Document {
998
1158
  return Ok(());
999
1159
  }
1000
1160
 
1001
- let mut sortable: Vec<(Vec<String>, String)> = entries
1161
+ let (groups, range) = collect_groups_with_range(sequence.syntax());
1162
+
1163
+ let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
1002
1164
  .iter()
1003
- .map(|entry| {
1165
+ .zip(groups)
1166
+ .map(|(entry, group)| {
1004
1167
  let sort_values = if sort_fields.is_empty() {
1005
1168
  vec![entry
1006
1169
  .flow()
@@ -1016,11 +1179,11 @@ impl Document {
1016
1179
  .collect()
1017
1180
  };
1018
1181
 
1019
- (sort_values, entry.syntax().text().to_string())
1182
+ (sort_values, group)
1020
1183
  })
1021
1184
  .collect();
1022
1185
 
1023
- let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
1186
+ let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1024
1187
 
1025
1188
  sortable.sort_by(|(values_a, _), (values_b, _)| {
1026
1189
  for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
@@ -1051,9 +1214,9 @@ impl Document {
1051
1214
  std::cmp::Ordering::Equal
1052
1215
  });
1053
1216
 
1054
- let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
1217
+ let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1055
1218
 
1056
- if sorted_texts == original_order {
1219
+ if sorted_bodies == original_bodies {
1057
1220
  return Ok(());
1058
1221
  }
1059
1222
 
@@ -1062,10 +1225,10 @@ impl Document {
1062
1225
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
1063
1226
  .unwrap_or_default();
1064
1227
 
1065
- let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
1066
- let sequence_range = sequence.syntax().text_range();
1228
+ let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
1229
+ let sequence_text = rebuild_from_groups(&sorted_groups, &indent);
1067
1230
 
1068
- self.apply_edit(sequence_range, &sequence_text)
1231
+ self.apply_edit(range, &sequence_text)
1069
1232
  }
1070
1233
 
1071
1234
  fn sort_each_items(
@@ -1080,7 +1243,7 @@ impl Document {
1080
1243
  (dot_path, "")
1081
1244
  };
1082
1245
 
1083
- let parent_nodes = self.navigate_to_many(parent_path);
1246
+ let parent_nodes = self.navigate_all(parent_path);
1084
1247
  let source = self.root.text().to_string();
1085
1248
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1086
1249
 
@@ -1103,9 +1266,12 @@ impl Document {
1103
1266
  continue;
1104
1267
  }
1105
1268
 
1106
- let mut sortable: Vec<(Vec<String>, String)> = entries
1269
+ let (groups, group_range) = collect_groups_with_range(sequence.syntax());
1270
+
1271
+ let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
1107
1272
  .iter()
1108
- .map(|entry| {
1273
+ .zip(groups)
1274
+ .map(|(entry, group)| {
1109
1275
  let sort_values = if sort_fields.is_empty() {
1110
1276
  vec![entry
1111
1277
  .flow()
@@ -1121,11 +1287,11 @@ impl Document {
1121
1287
  .collect()
1122
1288
  };
1123
1289
 
1124
- (sort_values, entry.syntax().text().to_string())
1290
+ (sort_values, group)
1125
1291
  })
1126
1292
  .collect();
1127
1293
 
1128
- let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
1294
+ let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1129
1295
 
1130
1296
  sortable.sort_by(|(values_a, _), (values_b, _)| {
1131
1297
  for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
@@ -1156,9 +1322,9 @@ impl Document {
1156
1322
  std::cmp::Ordering::Equal
1157
1323
  });
1158
1324
 
1159
- let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
1325
+ let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1160
1326
 
1161
- if sorted_texts == original_order {
1327
+ if sorted_bodies == original_bodies {
1162
1328
  continue;
1163
1329
  }
1164
1330
 
@@ -1167,9 +1333,9 @@ impl Document {
1167
1333
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
1168
1334
  .unwrap_or_default();
1169
1335
 
1170
- let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
1171
-
1172
- edits.push((sequence.syntax().text_range(), sequence_text));
1336
+ let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
1337
+ let sequence_text = rebuild_from_groups(&sorted_groups, &indent);
1338
+ edits.push((group_range, sequence_text));
1173
1339
  }
1174
1340
  }
1175
1341
 
@@ -1196,40 +1362,43 @@ impl Document {
1196
1362
  }
1197
1363
 
1198
1364
  pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
1199
- let keys: Vec<&str> = dot_path.split('.').collect();
1200
- let current_node = self.navigate_to_path(&keys)?;
1201
-
1202
- let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
1203
- Some(sequence) => sequence,
1204
- None => return Ok(()),
1365
+ let nodes = if dot_path.contains('[') {
1366
+ self.navigate_all(dot_path)
1367
+ } else {
1368
+ vec![self.navigate(dot_path)?]
1205
1369
  };
1206
1370
 
1207
- let entries: Vec<_> = sequence.entries().collect();
1371
+ let mut edits: Vec<(TextRange, String)> = Vec::new();
1208
1372
 
1209
- if entries.len() <= 1 {
1210
- return Ok(());
1211
- }
1373
+ for current_node in &nodes {
1374
+ let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
1375
+ Some(sequence) => sequence,
1376
+ None => continue,
1377
+ };
1212
1378
 
1213
- let source = self.root.text().to_string();
1214
- let mut edits: Vec<(TextRange, String)> = Vec::new();
1379
+ let entries: Vec<_> = sequence.entries().collect();
1215
1380
 
1216
- for entry in entries.iter().skip(1) {
1217
- if let Some(whitespace_token) = preceding_whitespace_token(entry.syntax()) {
1218
- let whitespace_text = whitespace_token.text();
1381
+ if entries.len() <= 1 {
1382
+ continue;
1383
+ }
1219
1384
 
1220
- let newline_count = whitespace_text.chars().filter(|character| *character == '\n').count();
1385
+ for entry in entries.iter().skip(1) {
1386
+ if let Some(whitespace_token) = preceding_whitespace_token(entry.syntax()) {
1387
+ let whitespace_text = whitespace_token.text();
1388
+ let newline_count = whitespace_text.chars().filter(|character| *character == '\n').count();
1221
1389
 
1222
- let indent = whitespace_text
1223
- .rfind('\n')
1224
- .map(|position| &whitespace_text[position + 1..])
1225
- .unwrap_or("");
1390
+ let indent = whitespace_text
1391
+ .rfind('\n')
1392
+ .map(|position| &whitespace_text[position + 1..])
1393
+ .unwrap_or("");
1226
1394
 
1227
- let desired_newlines = blank_lines + 1;
1395
+ let desired_newlines = blank_lines + 1;
1228
1396
 
1229
- if newline_count != desired_newlines {
1230
- let new_whitespace = format!("{}{}", "\n".repeat(desired_newlines), indent);
1397
+ if newline_count != desired_newlines {
1398
+ let new_whitespace = format!("{}{}", "\n".repeat(desired_newlines), indent);
1231
1399
 
1232
- edits.push((whitespace_token.text_range(), new_whitespace));
1400
+ edits.push((whitespace_token.text_range(), new_whitespace));
1401
+ }
1233
1402
  }
1234
1403
  }
1235
1404
  }
@@ -1238,8 +1407,9 @@ impl Document {
1238
1407
  return Ok(());
1239
1408
  }
1240
1409
 
1241
- edits.reverse();
1410
+ edits.sort_by_key(|edit| std::cmp::Reverse(edit.0.start()));
1242
1411
 
1412
+ let source = self.root.text().to_string();
1243
1413
  let mut new_source = source;
1244
1414
 
1245
1415
  for (range, replacement) in edits {
@@ -1259,20 +1429,19 @@ impl Document {
1259
1429
  pub fn enforce_key_style(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
1260
1430
  let source = self.root.text().to_string();
1261
1431
 
1262
- let scope_node = match dot_path {
1263
- Some(path) if !path.is_empty() => {
1264
- let keys: Vec<&str> = path.split('.').collect();
1265
- self.navigate_to_path(&keys)?
1266
- }
1267
- _ => self.root.clone(),
1432
+ let scope_ranges: Vec<TextRange> = match dot_path {
1433
+ Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
1434
+ _ => vec![self.root.text_range()],
1268
1435
  };
1269
1436
 
1270
- let scope_range = scope_node.text_range();
1271
1437
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1272
1438
 
1273
1439
  for element in self.root.descendants_with_tokens() {
1274
1440
  if let Some(token) = element.into_token() {
1275
- if !scope_range.contains_range(token.text_range()) {
1441
+ if !scope_ranges
1442
+ .iter()
1443
+ .any(|range| range.contains_range(token.text_range()))
1444
+ {
1276
1445
  continue;
1277
1446
  }
1278
1447
 
@@ -1362,21 +1531,19 @@ impl Document {
1362
1531
  pub fn enforce_quotes_at(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
1363
1532
  let source = self.root.text().to_string();
1364
1533
 
1365
- let scope_node = match dot_path {
1366
- Some(path) if !path.is_empty() => {
1367
- let keys: Vec<&str> = path.split('.').collect();
1368
- self.navigate_to_path(&keys)?
1369
- }
1370
- _ => self.root.clone(),
1534
+ let scope_ranges: Vec<TextRange> = match dot_path {
1535
+ Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
1536
+ _ => vec![self.root.text_range()],
1371
1537
  };
1372
1538
 
1373
- let scope_range = scope_node.text_range();
1374
-
1375
1539
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1376
1540
 
1377
1541
  for element in self.root.descendants_with_tokens() {
1378
1542
  if let Some(token) = element.into_token() {
1379
- if !scope_range.contains_range(token.text_range()) {
1543
+ if !scope_ranges
1544
+ .iter()
1545
+ .any(|range| range.contains_range(token.text_range()))
1546
+ {
1380
1547
  continue;
1381
1548
  }
1382
1549
 
@@ -1493,8 +1660,71 @@ impl Document {
1493
1660
  Ok(())
1494
1661
  }
1495
1662
 
1496
- pub fn navigate_to_many(&self, dot_path: &str) -> Vec<SyntaxNode> {
1497
- let segments = parse_path_segments(dot_path);
1663
+ pub fn navigate(&self, dot_path: &str) -> Result<SyntaxNode, YerbaError> {
1664
+ Self::validate_path(dot_path)?;
1665
+
1666
+ if dot_path.is_empty() {
1667
+ let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
1668
+
1669
+ let document = root
1670
+ .documents()
1671
+ .next()
1672
+ .ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
1673
+
1674
+ return Ok(document.syntax().clone());
1675
+ }
1676
+
1677
+ let nodes = self.navigate_all(dot_path);
1678
+
1679
+ match nodes.len() {
1680
+ 0 => Err(YerbaError::PathNotFound(dot_path.to_string())),
1681
+ 1 => Ok(nodes.into_iter().next().unwrap()),
1682
+ _ => Err(YerbaError::PathNotFound(format!(
1683
+ "{} (matched {} nodes, expected 1)",
1684
+ dot_path,
1685
+ nodes.len()
1686
+ ))),
1687
+ }
1688
+ }
1689
+
1690
+ pub fn validate_path(dot_path: &str) -> Result<(), YerbaError> {
1691
+ if dot_path.ends_with('.') {
1692
+ return Err(YerbaError::ParseError(format!(
1693
+ "invalid path: trailing dot in '{}'",
1694
+ dot_path
1695
+ )));
1696
+ }
1697
+
1698
+ if dot_path.contains("..") {
1699
+ return Err(YerbaError::ParseError(format!(
1700
+ "invalid path: double dot in '{}'",
1701
+ dot_path
1702
+ )));
1703
+ }
1704
+
1705
+ if dot_path.starts_with('.') {
1706
+ return Err(YerbaError::ParseError(format!(
1707
+ "invalid path: leading dot in '{}'",
1708
+ dot_path
1709
+ )));
1710
+ }
1711
+
1712
+ if dot_path.contains('[') && !dot_path.contains(']') {
1713
+ return Err(YerbaError::ParseError(format!(
1714
+ "invalid path: unclosed bracket in '{}'",
1715
+ dot_path
1716
+ )));
1717
+ }
1718
+
1719
+ Ok(())
1720
+ }
1721
+
1722
+ pub fn navigate_all(&self, dot_path: &str) -> Vec<SyntaxNode> {
1723
+ if Document::validate_path(dot_path).is_err() {
1724
+ return Vec::new();
1725
+ }
1726
+
1727
+ let parsed = crate::selector::Selector::parse(dot_path);
1498
1728
 
1499
1729
  let root = match Root::cast(self.root.clone()) {
1500
1730
  Some(root) => root,
@@ -1508,7 +1738,7 @@ impl Document {
1508
1738
 
1509
1739
  let mut current_nodes = vec![document.syntax().clone()];
1510
1740
 
1511
- if segments.is_empty() {
1741
+ if parsed.is_empty() {
1512
1742
  if let Some(sequence) = document.syntax().descendants().find_map(BlockSeq::cast) {
1513
1743
  current_nodes = sequence.entries().map(|entry| entry.syntax().clone()).collect();
1514
1744
  }
@@ -1516,7 +1746,7 @@ impl Document {
1516
1746
  return current_nodes;
1517
1747
  }
1518
1748
 
1519
- for segment in &segments {
1749
+ for segment in parsed.segments() {
1520
1750
  let mut next_nodes = Vec::new();
1521
1751
 
1522
1752
  for node in &current_nodes {
@@ -1533,37 +1763,6 @@ impl Document {
1533
1763
  current_nodes
1534
1764
  }
1535
1765
 
1536
- fn navigate_to_path(&self, keys: &[&str]) -> Result<SyntaxNode, YerbaError> {
1537
- let keys: Vec<&&str> = keys.iter().filter(|key| !key.is_empty()).collect();
1538
- let path_string = keys.iter().map(|key| **key).collect::<Vec<_>>().join(".");
1539
-
1540
- let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1541
-
1542
- let document = root
1543
- .documents()
1544
- .next()
1545
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1546
-
1547
- let mut current_node = document.syntax().clone();
1548
-
1549
- for key in &keys {
1550
- let map = current_node
1551
- .descendants()
1552
- .find_map(BlockMap::cast)
1553
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1554
-
1555
- let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1556
-
1557
- let map_value = entry
1558
- .value()
1559
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1560
-
1561
- current_node = map_value.syntax().clone();
1562
- }
1563
-
1564
- Ok(current_node)
1565
- }
1566
-
1567
1766
  fn replace_token(&mut self, token: &SyntaxToken, new_text: &str) -> Result<(), YerbaError> {
1568
1767
  let range = token.text_range();
1569
1768
 
@@ -1578,22 +1777,51 @@ impl Document {
1578
1777
  }
1579
1778
 
1580
1779
  fn remove_node(&mut self, node: &SyntaxNode) -> Result<(), YerbaError> {
1780
+ let inline_comment = self.find_inline_comment(node);
1581
1781
  let range = removal_range(node);
1582
1782
 
1583
- self.apply_edit(range, "")
1783
+ if let Some((comment_text, comment_end)) = inline_comment {
1784
+ let indent = preceding_whitespace_indent(node);
1785
+ let replacement = format!("\n{}{}", indent, comment_text);
1786
+ let expanded_range = TextRange::new(range.start(), comment_end);
1787
+
1788
+ self.apply_edit(expanded_range, &replacement)
1789
+ } else {
1790
+ self.apply_edit(range, "")
1791
+ }
1584
1792
  }
1585
1793
 
1586
- fn reorder_entries<T>(
1587
- &mut self,
1588
- entries: &[T],
1589
- from: usize,
1590
- to: usize,
1591
- get_text: impl Fn(&T) -> String,
1592
- get_indent: impl Fn(&T) -> String,
1593
- range: TextRange,
1594
- ) -> Result<(), YerbaError>
1794
+ fn find_inline_comment(&self, node: &SyntaxNode) -> Option<(String, rowan::TextSize)> {
1795
+ let mut sibling = node.next_sibling_or_token();
1796
+
1797
+ while let Some(ref element) = sibling {
1798
+ match element {
1799
+ rowan::NodeOrToken::Token(token) => {
1800
+ if token.kind() == SyntaxKind::COMMENT {
1801
+ return Some((token.text().to_string(), token.text_range().end()));
1802
+ } else if token.kind() == SyntaxKind::WHITESPACE {
1803
+ if token.text().contains('\n') {
1804
+ return None;
1805
+ }
1806
+ } else {
1807
+ return None;
1808
+ }
1809
+ }
1810
+ _ => return None,
1811
+ }
1812
+
1813
+ sibling = match element {
1814
+ rowan::NodeOrToken::Token(token) => token.next_sibling_or_token(),
1815
+ rowan::NodeOrToken::Node(node) => node.next_sibling_or_token(),
1816
+ };
1817
+ }
1818
+
1819
+ None
1820
+ }
1821
+
1822
+ fn reorder_entries<T>(&mut self, parent: &SyntaxNode, entries: &[T], from: usize, to: usize) -> Result<(), YerbaError>
1595
1823
  where
1596
- T: rowan::ast::AstNode,
1824
+ T: rowan::ast::AstNode<Language = yaml_parser::YamlLanguage>,
1597
1825
  {
1598
1826
  let length = entries.len();
1599
1827
 
@@ -1605,15 +1833,18 @@ impl Document {
1605
1833
  return Err(YerbaError::IndexOutOfBounds(to, length));
1606
1834
  }
1607
1835
 
1608
- let entry_texts: Vec<String> = entries.iter().map(&get_text).collect();
1836
+ let (groups, range) = collect_groups_with_range(parent);
1609
1837
 
1610
- let mut reordered = entry_texts.clone();
1838
+ let mut reordered = groups.clone();
1611
1839
  let item = reordered.remove(from);
1612
-
1613
1840
  reordered.insert(to, item);
1614
1841
 
1615
- let indent = entries.get(1).map(&get_indent).unwrap_or_default();
1616
- let text = rebuild_entries(reordered.iter().map(|text| text.as_str()), &indent);
1842
+ let indent = entries
1843
+ .get(1)
1844
+ .map(|entry| preceding_whitespace_indent(entry.syntax()))
1845
+ .unwrap_or_default();
1846
+
1847
+ let text = rebuild_from_groups(&reordered, &indent);
1617
1848
 
1618
1849
  self.apply_edit(range, &text)
1619
1850
  }
@@ -1640,6 +1871,96 @@ impl std::fmt::Display for Document {
1640
1871
  }
1641
1872
  }
1642
1873
 
1874
+ pub fn node_to_yaml_value(node: &SyntaxNode) -> serde_yaml::Value {
1875
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1876
+ let map_position = node
1877
+ .descendants()
1878
+ .find_map(BlockMap::cast)
1879
+ .map(|map| map.syntax().text_range().start());
1880
+
1881
+ let sequence_position = sequence.syntax().text_range().start();
1882
+
1883
+ if map_position.is_none() || sequence_position <= map_position.unwrap() {
1884
+ let values: Vec<serde_yaml::Value> = sequence
1885
+ .entries()
1886
+ .map(|entry| node_to_yaml_value(entry.syntax()))
1887
+ .collect();
1888
+
1889
+ return serde_yaml::Value::Sequence(values);
1890
+ }
1891
+ }
1892
+
1893
+ if let Some(map) = node.descendants().find_map(BlockMap::cast) {
1894
+ let mut mapping = serde_yaml::Mapping::new();
1895
+
1896
+ for entry in map.entries() {
1897
+ let key = entry
1898
+ .key()
1899
+ .and_then(|key_node| extract_scalar_text(key_node.syntax()))
1900
+ .unwrap_or_default();
1901
+
1902
+ let value = entry
1903
+ .value()
1904
+ .map(|value_node| node_to_yaml_value(value_node.syntax()))
1905
+ .unwrap_or(serde_yaml::Value::Null);
1906
+
1907
+ mapping.insert(serde_yaml::Value::String(key), value);
1908
+ }
1909
+
1910
+ return serde_yaml::Value::Mapping(mapping);
1911
+ }
1912
+
1913
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1914
+ let values: Vec<serde_yaml::Value> = sequence
1915
+ .entries()
1916
+ .map(|entry| node_to_yaml_value(entry.syntax()))
1917
+ .collect();
1918
+
1919
+ return serde_yaml::Value::Sequence(values);
1920
+ }
1921
+
1922
+ if let Some(block_scalar) = node
1923
+ .descendants()
1924
+ .find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR)
1925
+ {
1926
+ let text = block_scalar
1927
+ .descendants_with_tokens()
1928
+ .filter_map(|element| element.into_token())
1929
+ .find(|token| token.kind() == SyntaxKind::BLOCK_SCALAR_TEXT)
1930
+ .map(|token| token.text().to_string())
1931
+ .unwrap_or_default();
1932
+
1933
+ return serde_yaml::Value::String(text);
1934
+ }
1935
+
1936
+ if let Some(scalar) = extract_scalar(node) {
1937
+ use crate::syntax::{detect_yaml_type, is_yaml_truthy, YerbaValueType};
1938
+
1939
+ return match detect_yaml_type(&scalar) {
1940
+ YerbaValueType::Null => serde_yaml::Value::Null,
1941
+ YerbaValueType::Boolean => serde_yaml::Value::Bool(is_yaml_truthy(&scalar.text)),
1942
+
1943
+ YerbaValueType::Integer => scalar
1944
+ .text
1945
+ .parse::<i64>()
1946
+ .map(|n| serde_yaml::Value::Number(serde_yaml::Number::from(n)))
1947
+ .unwrap_or(serde_yaml::Value::String(scalar.text)),
1948
+
1949
+ YerbaValueType::Float => scalar
1950
+ .text
1951
+ .parse::<f64>()
1952
+ .map(|n| serde_yaml::Value::Number(serde_yaml::Number::from(n)))
1953
+ .unwrap_or(serde_yaml::Value::String(scalar.text)),
1954
+
1955
+ YerbaValueType::String => serde_yaml::Value::String(scalar.text),
1956
+ };
1957
+ }
1958
+
1959
+ let text = node.text().to_string();
1960
+
1961
+ serde_yaml::from_str(&text).unwrap_or(serde_yaml::Value::String(text))
1962
+ }
1963
+
1643
1964
  fn parse_condition(condition: &str) -> Option<(String, &str, String)> {
1644
1965
  let (left, operator, right) = if let Some(index) = condition.find(" not_contains ") {
1645
1966
  (
@@ -1666,140 +1987,250 @@ fn parse_condition(condition: &str) -> Option<(String, &str, String)> {
1666
1987
  Some((left.to_string(), operator, right.to_string()))
1667
1988
  }
1668
1989
 
1669
- fn parse_path_segments(path: &str) -> Vec<&str> {
1670
- if path.is_empty() {
1671
- return Vec::new();
1672
- }
1990
+ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment) -> Vec<SyntaxNode> {
1991
+ use crate::selector::SelectorSegment;
1673
1992
 
1674
- let mut segments = Vec::new();
1675
- let mut rest = path;
1993
+ match segment {
1994
+ SelectorSegment::AllItems => {
1995
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1996
+ sequence.entries().map(|entry| entry.syntax().clone()).collect()
1997
+ } else {
1998
+ Vec::new()
1999
+ }
2000
+ }
1676
2001
 
1677
- while !rest.is_empty() {
1678
- if rest.starts_with('[') {
1679
- if let Some(close) = rest.find(']') {
1680
- segments.push(&rest[..close + 1]);
1681
- rest = &rest[close + 1..];
2002
+ SelectorSegment::Index(index) => {
2003
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
2004
+ sequence
2005
+ .entries()
2006
+ .nth(*index)
2007
+ .map(|entry| vec![entry.syntax().clone()])
2008
+ .unwrap_or_default()
2009
+ } else {
2010
+ Vec::new()
2011
+ }
2012
+ }
1682
2013
 
1683
- if rest.starts_with('.') {
1684
- rest = &rest[1..];
2014
+ SelectorSegment::Key(key) => {
2015
+ if let Some(map) = node.descendants().find_map(BlockMap::cast) {
2016
+ if let Some(entry) = find_entry_by_key(&map, key) {
2017
+ if let Some(value) = entry.value() {
2018
+ return vec![value.syntax().clone()];
2019
+ }
1685
2020
  }
1686
- } else {
1687
- segments.push(rest);
1688
- break;
1689
2021
  }
1690
- } else {
1691
- let dot_index = rest.find('.');
1692
- let bracket_index = rest.find('[');
1693
-
1694
- let split_at = match (dot_index, bracket_index) {
1695
- (Some(dot), Some(bracket)) => Some(dot.min(bracket)),
1696
- (Some(dot), None) => Some(dot),
1697
- (None, Some(bracket)) => Some(bracket),
1698
- (None, None) => None,
1699
- };
1700
2022
 
1701
- match split_at {
1702
- Some(index) => {
1703
- let segment = &rest[..index];
2023
+ Vec::new()
2024
+ }
2025
+ }
2026
+ }
1704
2027
 
1705
- if !segment.is_empty() {
1706
- segments.push(segment);
1707
- }
2028
+ fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
2029
+ let parsed = crate::selector::Selector::parse(path);
2030
+ let mut current_nodes = vec![node.clone()];
1708
2031
 
1709
- rest = &rest[index..];
2032
+ for segment in parsed.segments() {
2033
+ let mut next_nodes = Vec::new();
1710
2034
 
1711
- if rest.starts_with('.') {
1712
- rest = &rest[1..];
1713
- }
1714
- }
2035
+ for current in &current_nodes {
2036
+ next_nodes.extend(resolve_segment(current, segment));
2037
+ }
1715
2038
 
1716
- None => {
1717
- segments.push(rest);
1718
- break;
1719
- }
1720
- }
2039
+ current_nodes = next_nodes;
2040
+
2041
+ if current_nodes.is_empty() {
2042
+ break;
1721
2043
  }
1722
2044
  }
1723
2045
 
1724
- segments
2046
+ current_nodes
1725
2047
  }
1726
2048
 
1727
- fn parse_bracket_index(segment: &str) -> Option<usize> {
1728
- if segment == "[]" {
1729
- return None;
1730
- }
2049
+ #[derive(Debug, Clone)]
2050
+ struct EntryGroup {
2051
+ separator: String,
2052
+ preceding: String,
2053
+ body: String,
2054
+ }
1731
2055
 
1732
- segment
1733
- .strip_prefix('[')
1734
- .and_then(|rest| rest.strip_suffix(']'))
1735
- .and_then(|inner| inner.parse::<usize>().ok())
2056
+ impl EntryGroup {
2057
+ fn full_text(&self) -> String {
2058
+ if self.preceding.is_empty() {
2059
+ self.body.clone()
2060
+ } else {
2061
+ format!("{}\n{}", self.preceding, self.body)
2062
+ }
2063
+ }
1736
2064
  }
1737
2065
 
1738
- fn resolve_segment(node: &SyntaxNode, segment: &str) -> Vec<SyntaxNode> {
1739
- if segment.starts_with('[') {
1740
- if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1741
- match parse_bracket_index(segment) {
1742
- None => sequence.entries().map(|entry| entry.syntax().clone()).collect(),
2066
+ fn collect_entry_groups(parent: &SyntaxNode) -> Vec<EntryGroup> {
2067
+ let mut groups: Vec<EntryGroup> = Vec::new();
2068
+ let mut buffer = String::new();
1743
2069
 
1744
- Some(index) => sequence
1745
- .entries()
1746
- .nth(index)
1747
- .map(|entry| vec![entry.syntax().clone()])
1748
- .unwrap_or_default(),
2070
+ for child in parent.children_with_tokens() {
2071
+ let is_entry = child.as_node().is_some()
2072
+ && matches!(
2073
+ child.as_node().unwrap().kind(),
2074
+ SyntaxKind::BLOCK_MAP_ENTRY | SyntaxKind::BLOCK_SEQ_ENTRY
2075
+ );
2076
+
2077
+ if is_entry {
2078
+ let entry_text = child.as_node().unwrap().text().to_string();
2079
+
2080
+ if groups.is_empty() {
2081
+ let preceding = buffer.trim_start_matches('\n').to_string();
2082
+
2083
+ groups.push(EntryGroup {
2084
+ separator: String::new(),
2085
+ preceding,
2086
+ body: entry_text,
2087
+ });
2088
+ } else {
2089
+ let (trailing, separator, preceding) = split_at_blank_line(&buffer);
2090
+
2091
+ if let Some(last) = groups.last_mut() {
2092
+ last.body.push_str(&trailing);
2093
+ }
2094
+
2095
+ groups.push(EntryGroup {
2096
+ separator,
2097
+ preceding,
2098
+ body: entry_text,
2099
+ });
1749
2100
  }
2101
+
2102
+ buffer.clear();
1750
2103
  } else {
1751
- Vec::new()
2104
+ let text = match &child {
2105
+ rowan::NodeOrToken::Node(node) => node.text().to_string(),
2106
+ rowan::NodeOrToken::Token(token) => token.text().to_string(),
2107
+ };
2108
+
2109
+ buffer.push_str(&text);
1752
2110
  }
1753
- } else {
1754
- if let Some(map) = node.descendants().find_map(BlockMap::cast) {
1755
- if let Some(entry) = find_entry_by_key(&map, segment) {
1756
- if let Some(value) = entry.value() {
1757
- return vec![value.syntax().clone()];
1758
- }
1759
- }
2111
+ }
2112
+
2113
+ if let Some(last) = groups.last_mut() {
2114
+ let trimmed = buffer.trim_end_matches(['\n', ' ', '\t']);
2115
+
2116
+ if !trimmed.is_empty() {
2117
+ last.body.push_str(trimmed);
1760
2118
  }
2119
+ }
2120
+
2121
+ for group in &mut groups {
2122
+ let trimmed = group.body.trim_end_matches(['\n', ' ', '\t']);
1761
2123
 
1762
- Vec::new()
2124
+ group.body = trimmed.to_string();
1763
2125
  }
2126
+
2127
+ groups
1764
2128
  }
1765
2129
 
1766
- fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
1767
- let segments = parse_path_segments(path);
1768
- let mut current_nodes = vec![node.clone()];
2130
+ fn split_at_blank_line(text: &str) -> (String, String, String) {
2131
+ if let Some(position) = text.find("\n\n") {
2132
+ let trailing = text[..position].to_string();
2133
+ let rest = &text[position..];
2134
+ let content_start = rest.len() - rest.trim_start_matches('\n').len();
2135
+ let separator = rest[..content_start].to_string();
2136
+ let preceding = rest[content_start..].trim_end_matches(['\n', ' ', '\t']).to_string();
1769
2137
 
1770
- for segment in &segments {
1771
- let mut next_nodes = Vec::new();
2138
+ (trailing, separator, preceding)
2139
+ } else {
2140
+ (text.to_string(), String::new(), String::new())
2141
+ }
2142
+ }
1772
2143
 
1773
- for current in &current_nodes {
1774
- next_nodes.extend(resolve_segment(current, segment));
1775
- }
2144
+ fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String, Option<rowan::TextSize>) {
2145
+ let mut comments: Vec<String> = Vec::new();
2146
+ let mut earliest_start = None;
2147
+ let mut node = parent.clone();
2148
+
2149
+ loop {
2150
+ let mut sibling = node.prev_sibling_or_token();
2151
+
2152
+ while let Some(ref element) = sibling {
2153
+ match element {
2154
+ rowan::NodeOrToken::Token(token) => {
2155
+ if token.kind() == SyntaxKind::COMMENT {
2156
+ comments.push(token.text().to_string());
2157
+ earliest_start = Some(token.text_range().start());
2158
+ } else if token.kind() == SyntaxKind::WHITESPACE {
2159
+ // Keep looking past whitespace
2160
+ } else {
2161
+ break;
2162
+ }
2163
+ }
2164
+ _ => break,
2165
+ }
1776
2166
 
1777
- current_nodes = next_nodes;
2167
+ sibling = match element {
2168
+ rowan::NodeOrToken::Token(token) => token.prev_sibling_or_token(),
2169
+ rowan::NodeOrToken::Node(node) => node.prev_sibling_or_token(),
2170
+ };
2171
+ }
1778
2172
 
1779
- if current_nodes.is_empty() {
2173
+ if !comments.is_empty() {
1780
2174
  break;
1781
2175
  }
2176
+
2177
+ match node.parent() {
2178
+ Some(parent)
2179
+ if parent.kind() == SyntaxKind::BLOCK
2180
+ || parent.kind() == SyntaxKind::DOCUMENT
2181
+ || parent.kind() == SyntaxKind::BLOCK_MAP_VALUE
2182
+ || parent.kind() == SyntaxKind::BLOCK_SEQ_ENTRY =>
2183
+ {
2184
+ node = parent
2185
+ }
2186
+ _ => break,
2187
+ }
1782
2188
  }
1783
2189
 
1784
- current_nodes
2190
+ comments.reverse();
2191
+ (comments.join("\n"), earliest_start)
1785
2192
  }
1786
2193
 
1787
- fn byte_offset_to_line(source: &str, offset: usize) -> usize {
1788
- source[..offset.min(source.len())]
1789
- .chars()
1790
- .filter(|character| *character == '\n')
1791
- .count()
1792
- + 1
2194
+ fn collect_groups_with_range(parent: &SyntaxNode) -> (Vec<EntryGroup>, TextRange) {
2195
+ let mut groups = collect_entry_groups(parent);
2196
+
2197
+ let (sibling_comments, earliest_start) = collect_preceding_sibling_comments(parent);
2198
+
2199
+ if !sibling_comments.is_empty() {
2200
+ if let Some(first) = groups.first_mut() {
2201
+ if first.preceding.is_empty() {
2202
+ first.preceding = sibling_comments;
2203
+ } else {
2204
+ first.preceding = format!("{}\n{}", sibling_comments, first.preceding);
2205
+ }
2206
+ }
2207
+ }
2208
+
2209
+ let range = match earliest_start {
2210
+ Some(start) => TextRange::new(start, parent.text_range().end()),
2211
+ None => parent.text_range(),
2212
+ };
2213
+
2214
+ (groups, range)
1793
2215
  }
1794
2216
 
1795
- fn rebuild_entries<'a>(entries: impl Iterator<Item = &'a str>, indent: &str) -> String {
1796
- entries
2217
+ fn rebuild_from_groups(groups: &[EntryGroup], indent: &str) -> String {
2218
+ let default_separator = groups
2219
+ .iter()
2220
+ .find(|group| !group.separator.is_empty())
2221
+ .map(|group| group.separator.clone())
2222
+ .unwrap_or_else(|| "\n".to_string());
2223
+
2224
+ groups
2225
+ .iter()
1797
2226
  .enumerate()
1798
- .map(|(index, text)| {
2227
+ .map(|(index, group)| {
1799
2228
  if index == 0 {
1800
- text.to_string()
2229
+ group.full_text()
2230
+ } else if group.preceding.is_empty() {
2231
+ format!("{}{}{}", default_separator, indent, group.body)
1801
2232
  } else {
1802
- format!("\n{}{}", indent, text)
2233
+ format!("{}{}\n{}{}", default_separator, group.preceding, indent, group.body)
1803
2234
  }
1804
2235
  })
1805
2236
  .collect()