yerba 0.5.1-x86_64-linux-gnu → 0.6.0-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -126,23 +126,109 @@ impl Document {
126
126
  Self::validate_path(dot_path)?;
127
127
 
128
128
  if let Ok(current_node) = self.navigate(dot_path) {
129
- if current_node.descendants().find_map(BlockSeq::cast).is_some() {
129
+ if current_node.descendants().find_map(BlockSeq::cast).is_some()
130
+ || matches!(self.get_value(dot_path).as_ref(), Some(yaml_serde::Value::Sequence(sequence)) if sequence.is_empty())
131
+ {
130
132
  return self.insert_sequence_item(dot_path, value, position);
131
133
  }
132
134
  }
133
135
 
136
+ let selector = crate::selector::Selector::parse(dot_path);
137
+ let has_wildcard = selector.has_wildcard() || selector.has_brackets();
134
138
  let (parent_path, key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
135
139
 
140
+ if has_wildcard {
141
+ let count = self.navigate_all_compact(parent_path).len();
142
+
143
+ if count == 0 {
144
+ return Ok(());
145
+ }
146
+
147
+ for i in (0..count).rev() {
148
+ let nodes = self.navigate_all_compact(parent_path);
149
+
150
+ let node = match nodes.get(i) {
151
+ Some(node) => node.clone(),
152
+ None => continue,
153
+ };
154
+
155
+ let map = match node.descendants().find_map(BlockMap::cast) {
156
+ Some(map) => map,
157
+ None => continue,
158
+ };
159
+
160
+ if find_entry_by_key(&map, key).is_some() {
161
+ continue;
162
+ }
163
+
164
+ let entries: Vec<_> = map.entries().collect();
165
+
166
+ if entries.is_empty() {
167
+ continue;
168
+ }
169
+
170
+ let first_entry = entries.first().unwrap();
171
+
172
+ let start_col = {
173
+ let offset: usize = first_entry.syntax().text_range().start().into();
174
+ let source = self.to_string();
175
+ let before = &source[..offset];
176
+
177
+ offset - before.rfind('\n').map(|p| p + 1).unwrap_or(0)
178
+ };
179
+
180
+ let indent = " ".repeat(start_col);
181
+ let new_entry_text = format!("{}: {}", key, value);
182
+
183
+ match &position {
184
+ InsertPosition::After(target_key) => {
185
+ let target = find_entry_by_key(&map, target_key);
186
+ let after_node = target.map(|e| e.syntax().clone()).unwrap_or_else(|| entries.last().unwrap().syntax().clone());
187
+ let new_text = format!("\n{}{}", indent, new_entry_text);
188
+
189
+ self.insert_after_node(&after_node, &new_text)?;
190
+ }
191
+
192
+ InsertPosition::Before(target_key) => {
193
+ if let Some(target_entry) = find_entry_by_key(&map, target_key) {
194
+ let target_range = target_entry.syntax().text_range();
195
+ let replacement = format!("{}\n{}", new_entry_text, indent);
196
+ let insert_range = TextRange::new(target_range.start(), target_range.start());
197
+
198
+ self.apply_edit(insert_range, &replacement)?;
199
+ } else {
200
+ let last_entry = entries.last().unwrap();
201
+ let new_text = format!("\n{}{}", indent, new_entry_text);
202
+
203
+ self.insert_after_node(last_entry.syntax(), &new_text)?;
204
+ }
205
+ }
206
+
207
+ _ => {
208
+ let last_entry = entries.last().unwrap();
209
+ let new_text = format!("\n{}{}", indent, new_entry_text);
210
+
211
+ self.insert_after_node(last_entry.syntax(), &new_text)?;
212
+ }
213
+ }
214
+ }
215
+
216
+ return Ok(());
217
+ }
218
+
136
219
  self.insert_map_key(parent_path, key, value, position)
137
220
  }
138
221
 
139
222
  fn insert_sequence_item(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
140
223
  let current_node = self.navigate(dot_path)?;
141
224
 
142
- let sequence = current_node
143
- .descendants()
144
- .find_map(BlockSeq::cast)
145
- .ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
225
+ let Some(sequence) = current_node.descendants().find_map(BlockSeq::cast) else {
226
+ if matches!(self.get_value(dot_path).as_ref(), Some(yaml_serde::Value::Sequence(sequence)) if sequence.is_empty()) {
227
+ return self.replace_empty_inline_sequence(dot_path, value);
228
+ }
229
+
230
+ return Err(YerbaError::NotASequence(dot_path.to_string()));
231
+ };
146
232
 
147
233
  let entries: Vec<_> = sequence.entries().collect();
148
234
 
@@ -308,7 +394,34 @@ impl Document {
308
394
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
309
395
  .unwrap_or_default();
310
396
 
311
- let new_entry_text = format!("{}: {}", key, value);
397
+ let is_block_value = value.contains('\n') || value.starts_with("- ");
398
+ let new_entry_text = if is_block_value {
399
+ let value_indent = format!("{} ", indent);
400
+ let lines: Vec<&str> = value.lines().collect();
401
+
402
+ let min_indent = lines
403
+ .iter()
404
+ .filter(|line| !line.trim().is_empty())
405
+ .map(|line| line.len() - line.trim_start().len())
406
+ .min()
407
+ .unwrap_or(0);
408
+
409
+ let indented_lines: Vec<String> = lines
410
+ .iter()
411
+ .map(|line| {
412
+ if line.trim().is_empty() {
413
+ String::new()
414
+ } else {
415
+ let relative = &line[min_indent..];
416
+ format!("{}{}", value_indent, relative)
417
+ }
418
+ })
419
+ .collect();
420
+
421
+ format!("{}:\n{}", key, indented_lines.join("\n"))
422
+ } else {
423
+ format!("{}: {}", key, value)
424
+ };
312
425
 
313
426
  match position {
314
427
  InsertPosition::Last => {
@@ -381,4 +494,86 @@ impl Document {
381
494
  }
382
495
  }
383
496
  }
497
+
498
+ fn replace_empty_inline_sequence(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
499
+ let (parent_path, key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
500
+ let parent_node = self.navigate(parent_path)?;
501
+ let map = parent_node
502
+ .descendants()
503
+ .find_map(BlockMap::cast)
504
+ .ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
505
+ let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
506
+
507
+ let item_indent = format!("{} ", preceding_whitespace_indent(entry.syntax()));
508
+ let new_item = Self::format_sequence_item(value, &item_indent);
509
+ let current_node = self.navigate(dot_path)?;
510
+ let mut range = current_node.text_range();
511
+ let inline_comment = self.trailing_inline_comment(&current_node);
512
+
513
+ if let Some(previous) = current_node.prev_sibling_or_token().and_then(|element| element.into_token()) {
514
+ if previous.kind() == SyntaxKind::WHITESPACE && !previous.text().contains('\n') {
515
+ range = TextRange::new(previous.text_range().start(), range.end());
516
+ }
517
+ }
518
+
519
+ if let Some((_, comment_end)) = inline_comment {
520
+ range = TextRange::new(range.start(), comment_end);
521
+ }
522
+
523
+ let replacement = match inline_comment {
524
+ Some((comment_text, _)) => format!(" {}\n{}{}", comment_text, item_indent, new_item),
525
+ None => format!("\n{}{}", item_indent, new_item),
526
+ };
527
+
528
+ self.apply_edit(range, &replacement)
529
+ }
530
+
531
+ fn trailing_inline_comment(&self, node: &SyntaxNode) -> Option<(String, rowan::TextSize)> {
532
+ let source = self.root.text().to_string();
533
+ let start: usize = node.text_range().end().into();
534
+ let rest = &source[start..];
535
+ let line_end = rest.find('\n').unwrap_or(rest.len());
536
+ let trailing = &rest[..line_end];
537
+ let comment_start = trailing.find('#')?;
538
+
539
+ if !trailing[..comment_start].trim().is_empty() {
540
+ return None;
541
+ }
542
+
543
+ Some((trailing[comment_start..].to_string(), rowan::TextSize::from((start + line_end) as u32)))
544
+ }
545
+
546
+ fn format_sequence_item(value: &str, indent: &str) -> String {
547
+ if value.contains('\n') {
548
+ let item_indent = format!("{} ", indent);
549
+ let lines: Vec<&str> = value.split('\n').collect();
550
+
551
+ let min_indent = lines
552
+ .iter()
553
+ .skip(1)
554
+ .filter(|line| !line.trim().is_empty())
555
+ .map(|line| line.len() - line.trim_start().len())
556
+ .min()
557
+ .unwrap_or(0);
558
+
559
+ let indented: Vec<String> = lines
560
+ .iter()
561
+ .enumerate()
562
+ .map(|(index, line)| {
563
+ if index == 0 {
564
+ line.to_string()
565
+ } else if line.trim().is_empty() {
566
+ String::new()
567
+ } else {
568
+ let relative = &line[min_indent..];
569
+ format!("{}{}", item_indent, relative)
570
+ }
571
+ })
572
+ .collect();
573
+
574
+ format!("- {}", indented.join("\n"))
575
+ } else {
576
+ format!("- {}", value)
577
+ }
578
+ }
384
579
  }
@@ -5,7 +5,7 @@ mod insert;
5
5
  mod schema;
6
6
  mod set;
7
7
  mod sort;
8
- mod style;
8
+ pub mod style;
9
9
  mod unique;
10
10
  pub use unique::DuplicateInfo;
11
11
 
@@ -25,9 +25,9 @@ use std::fs;
25
25
  use std::path::{Path, PathBuf};
26
26
 
27
27
  use rowan::ast::AstNode;
28
- use rowan::TextRange;
28
+ use rowan::{TextRange, TextSize};
29
29
 
30
- use yaml_parser::ast::{BlockMap, BlockSeq, Root};
30
+ use yaml_parser::ast::{BlockMap, BlockMapEntry, BlockSeq, Root};
31
31
  use yaml_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
32
32
 
33
33
  use crate::error::YerbaError;
@@ -35,7 +35,7 @@ use crate::QuoteStyle;
35
35
 
36
36
  use crate::syntax::{
37
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,
38
+ preceding_whitespace_indent, preceding_whitespace_token, removal_range, unescape_double_quoted, unescape_single_quoted, ScalarValue,
39
39
  };
40
40
 
41
41
  #[derive(Debug, Clone)]
@@ -297,6 +297,45 @@ impl Document {
297
297
  self.apply_edit(range, text)
298
298
  }
299
299
 
300
+ fn remove_map_entry(&mut self, entry: &BlockMapEntry) -> Result<(), YerbaError> {
301
+ let entry_node = entry.syntax();
302
+ let entry_range = entry_node.text_range();
303
+
304
+ let has_block_value = entry
305
+ .value()
306
+ .map(|value| {
307
+ value
308
+ .syntax()
309
+ .descendants()
310
+ .any(|descendant| descendant.kind() == SyntaxKind::BLOCK_SEQ || descendant.kind() == SyntaxKind::BLOCK_MAP)
311
+ })
312
+ .unwrap_or(false);
313
+
314
+ if !has_block_value {
315
+ return self.remove_node(entry_node);
316
+ }
317
+
318
+ let end = if let Some(next_sibling) = entry_node.next_sibling() {
319
+ next_sibling.text_range().start()
320
+ } else {
321
+ entry_node.parent().map(|parent| parent.text_range().end()).unwrap_or(entry_range.end())
322
+ };
323
+
324
+ let start = if let Some(whitespace_token) = preceding_whitespace_token(entry_node) {
325
+ let whitespace_text = whitespace_token.text();
326
+ let whitespace_start = whitespace_token.text_range().start();
327
+
328
+ whitespace_text
329
+ .rfind('\n')
330
+ .map(|offset| whitespace_start + TextSize::from(offset as u32))
331
+ .unwrap_or(whitespace_start)
332
+ } else {
333
+ entry_range.start()
334
+ };
335
+
336
+ self.apply_edit(TextRange::new(start, end), "")
337
+ }
338
+
300
339
  fn remove_node(&mut self, node: &SyntaxNode) -> Result<(), YerbaError> {
301
340
  let inline_comment = self.find_inline_comment(node);
302
341
  let range = removal_range(node);
@@ -524,6 +563,17 @@ pub(crate) fn node_to_yaml_value(node: &SyntaxNode) -> yaml_serde::Value {
524
563
  return yaml_serde::Value::String(text);
525
564
  }
526
565
 
566
+ if node
567
+ .descendants()
568
+ .any(|descendant| descendant.kind() == SyntaxKind::FLOW_SEQ || descendant.kind() == SyntaxKind::FLOW_MAP)
569
+ {
570
+ let text = node.text().to_string().trim().to_string();
571
+
572
+ if let Ok(value) = yaml_serde::from_str(&text) {
573
+ return value;
574
+ }
575
+ }
576
+
527
577
  if let Some(scalar) = extract_scalar(node) {
528
578
  use crate::syntax::{detect_yaml_type, is_yaml_truthy, YerbaValueType};
529
579