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.
- checksums.yaml +4 -4
- data/README.md +1 -25
- data/exe/x86_64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +12 -0
- data/ext/yerba/yerba.c +74 -0
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/formatting.rb +61 -0
- data/lib/yerba/map.rb +50 -6
- data/lib/yerba/sequence.rb +20 -0
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/document/delete.rs +14 -1
- data/rust/src/document/get.rs +47 -22
- data/rust/src/document/insert.rs +201 -6
- data/rust/src/document/mod.rs +54 -4
- data/rust/src/document/style.rs +609 -2
- data/rust/src/ffi.rs +46 -0
- data/rust/src/lib.rs +1 -0
- data/rust/src/yaml_writer.rs +98 -0
- data/rust/src/yerbafile.rs +139 -4
- metadata +2 -2
data/rust/src/document/insert.rs
CHANGED
|
@@ -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
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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(¤t_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
|
}
|
data/rust/src/document/mod.rs
CHANGED
|
@@ -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
|
|