yerba 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/exe/yerba +2 -2
- data/ext/yerba/extconf.rb +54 -0
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +99 -0
- data/rust/Cargo.lock +429 -0
- data/rust/Cargo.toml +32 -0
- data/rust/rustfmt.toml +2 -0
- data/rust/src/document.rs +1779 -0
- data/rust/src/error.rs +50 -0
- data/rust/src/json.rs +169 -0
- data/rust/src/lib.rs +22 -0
- data/rust/src/main.rs +856 -0
- data/rust/src/quote_style.rs +42 -0
- data/rust/src/syntax.rs +186 -0
- data/rust/src/yerbafile.rs +563 -0
- data/yerba.gemspec +8 -3
- metadata +15 -3
- data/Rakefile +0 -12
|
@@ -0,0 +1,1779 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
|
|
4
|
+
use rowan::ast::AstNode;
|
|
5
|
+
use rowan::TextRange;
|
|
6
|
+
|
|
7
|
+
use yaml_parser::ast::{BlockMap, BlockSeq, Root};
|
|
8
|
+
use yaml_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
|
|
9
|
+
|
|
10
|
+
use crate::error::YerbaError;
|
|
11
|
+
use crate::QuoteStyle;
|
|
12
|
+
|
|
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,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
#[derive(Debug)]
|
|
20
|
+
pub struct FindResult {
|
|
21
|
+
pub text: String,
|
|
22
|
+
pub line: usize,
|
|
23
|
+
pub end_line: usize,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[derive(Debug, Clone)]
|
|
27
|
+
pub struct SortField {
|
|
28
|
+
pub path: String,
|
|
29
|
+
pub ascending: bool,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl SortField {
|
|
33
|
+
pub fn asc(path: &str) -> Self {
|
|
34
|
+
SortField {
|
|
35
|
+
path: path.to_string(),
|
|
36
|
+
ascending: true,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn desc(path: &str) -> Self {
|
|
41
|
+
SortField {
|
|
42
|
+
path: path.to_string(),
|
|
43
|
+
ascending: false,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn parse(input: &str) -> Self {
|
|
48
|
+
if let Some((path, direction)) = input.rsplit_once(':') {
|
|
49
|
+
match direction {
|
|
50
|
+
"desc" | "descending" => SortField::desc(path),
|
|
51
|
+
_ => SortField::asc(input),
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
SortField::asc(input)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub fn parse_list(input: &str) -> Vec<Self> {
|
|
59
|
+
input.split(',').map(|field| SortField::parse(field.trim())).collect()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[derive(Debug)]
|
|
64
|
+
pub enum InsertPosition {
|
|
65
|
+
At(usize),
|
|
66
|
+
Last,
|
|
67
|
+
Before(String),
|
|
68
|
+
After(String),
|
|
69
|
+
FromSortOrder(Vec<String>),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[derive(Debug)]
|
|
73
|
+
pub struct Document {
|
|
74
|
+
root: SyntaxNode,
|
|
75
|
+
path: Option<PathBuf>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl Document {
|
|
79
|
+
pub fn parse(source: &str) -> Result<Self, YerbaError> {
|
|
80
|
+
let tree = yaml_parser::parse(source).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
|
|
81
|
+
|
|
82
|
+
Ok(Document { root: tree, path: None })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn parse_file(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
|
|
86
|
+
let path = path.as_ref();
|
|
87
|
+
let source = fs::read_to_string(path)?;
|
|
88
|
+
|
|
89
|
+
let mut document = Self::parse(&source)?;
|
|
90
|
+
|
|
91
|
+
document.path = Some(path.to_path_buf());
|
|
92
|
+
|
|
93
|
+
Ok(document)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub fn get(&self, dot_path: &str) -> Option<String> {
|
|
97
|
+
if dot_path.contains('[') {
|
|
98
|
+
return self.get_all(dot_path).into_iter().next();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
102
|
+
let current_node = self.navigate_to_path(&keys).ok()?;
|
|
103
|
+
|
|
104
|
+
extract_scalar_text(¤t_node)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn get_all(&self, dot_path: &str) -> Vec<String> {
|
|
108
|
+
self
|
|
109
|
+
.navigate_to_many(dot_path)
|
|
110
|
+
.iter()
|
|
111
|
+
.filter_map(extract_scalar_text)
|
|
112
|
+
.collect()
|
|
113
|
+
}
|
|
114
|
+
|
|
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);
|
|
118
|
+
|
|
119
|
+
nodes
|
|
120
|
+
.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
|
+
}
|
|
131
|
+
})
|
|
132
|
+
.collect()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pub fn find_all(&self, dot_path: &str) -> Vec<FindResult> {
|
|
136
|
+
let source = self.root.text().to_string();
|
|
137
|
+
|
|
138
|
+
self
|
|
139
|
+
.navigate_to_many(dot_path)
|
|
140
|
+
.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
|
+
})
|
|
151
|
+
.collect()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn evaluate_condition_on_node(&self, node: &SyntaxNode, condition: &str) -> bool {
|
|
155
|
+
let condition = condition.trim();
|
|
156
|
+
|
|
157
|
+
let (left, operator, right) = match parse_condition(condition) {
|
|
158
|
+
Some(parts) => parts,
|
|
159
|
+
None => return false,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
let left_path = left.strip_prefix('.').unwrap_or(&left);
|
|
163
|
+
let target_nodes = navigate_from_node(node, left_path);
|
|
164
|
+
|
|
165
|
+
let values: Vec<String> = target_nodes.iter().filter_map(extract_scalar_text).collect();
|
|
166
|
+
|
|
167
|
+
match operator {
|
|
168
|
+
"==" => values.iter().any(|value| value == &right),
|
|
169
|
+
"!=" => values.iter().all(|value| value != &right),
|
|
170
|
+
"contains" => {
|
|
171
|
+
if values.iter().any(|value| value == &right || value.contains(&right)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for node in &target_nodes {
|
|
176
|
+
if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
|
|
177
|
+
for entry in sequence.entries() {
|
|
178
|
+
if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
|
|
179
|
+
if text == right {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
false
|
|
188
|
+
}
|
|
189
|
+
"not_contains" => {
|
|
190
|
+
for node in &target_nodes {
|
|
191
|
+
if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
|
|
192
|
+
for entry in sequence.entries() {
|
|
193
|
+
if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
|
|
194
|
+
if text == right {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
!values.iter().any(|value| value == &right || value.contains(&right))
|
|
203
|
+
}
|
|
204
|
+
_ => false,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
pub fn exists(&self, dot_path: &str) -> bool {
|
|
209
|
+
if dot_path.contains('[') {
|
|
210
|
+
return !self.navigate_to_many(dot_path).is_empty();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
self.get(dot_path).is_some()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pub fn evaluate_condition(&self, parent_path: &str, condition: &str) -> bool {
|
|
217
|
+
let condition = condition.trim();
|
|
218
|
+
|
|
219
|
+
let (left, operator, right) = match parse_condition(condition) {
|
|
220
|
+
Some(parts) => parts,
|
|
221
|
+
None => return false,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
let full_path = if let Some(relative_key) = left.strip_prefix('.') {
|
|
225
|
+
if parent_path.is_empty() {
|
|
226
|
+
relative_key.to_string()
|
|
227
|
+
} else {
|
|
228
|
+
format!("{}.{}", parent_path, relative_key)
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
left.to_string()
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
let has_brackets = full_path.contains('[');
|
|
235
|
+
|
|
236
|
+
match operator {
|
|
237
|
+
"==" => {
|
|
238
|
+
if has_brackets {
|
|
239
|
+
self.get_all(&full_path).iter().any(|value| value == &right)
|
|
240
|
+
} else {
|
|
241
|
+
self.get(&full_path).unwrap_or_default() == right
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
"!=" => {
|
|
245
|
+
if has_brackets {
|
|
246
|
+
self.get_all(&full_path).iter().all(|value| value != &right)
|
|
247
|
+
} else {
|
|
248
|
+
self.get(&full_path).unwrap_or_default() != right
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
"contains" => {
|
|
252
|
+
if has_brackets {
|
|
253
|
+
self
|
|
254
|
+
.get_all(&full_path)
|
|
255
|
+
.iter()
|
|
256
|
+
.any(|value| value == &right || value.contains(&right))
|
|
257
|
+
} else {
|
|
258
|
+
let items = self.get_sequence_values(&full_path);
|
|
259
|
+
|
|
260
|
+
if !items.is_empty() {
|
|
261
|
+
items.iter().any(|item| item == &right)
|
|
262
|
+
} else {
|
|
263
|
+
self
|
|
264
|
+
.get(&full_path)
|
|
265
|
+
.map(|value| value.contains(&right))
|
|
266
|
+
.unwrap_or(false)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
"not_contains" => {
|
|
271
|
+
if has_brackets {
|
|
272
|
+
self
|
|
273
|
+
.get_all(&full_path)
|
|
274
|
+
.iter()
|
|
275
|
+
.all(|value| value != &right && !value.contains(&right))
|
|
276
|
+
} else {
|
|
277
|
+
let items = self.get_sequence_values(&full_path);
|
|
278
|
+
|
|
279
|
+
if !items.is_empty() {
|
|
280
|
+
!items.iter().any(|item| item == &right)
|
|
281
|
+
} else {
|
|
282
|
+
self
|
|
283
|
+
.get(&full_path)
|
|
284
|
+
.map(|value| !value.contains(&right))
|
|
285
|
+
.unwrap_or(true)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
_ => false,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
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) {
|
|
296
|
+
Ok(node) => node,
|
|
297
|
+
Err(_) => return Vec::new(),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
301
|
+
Some(sequence) => sequence,
|
|
302
|
+
None => return Vec::new(),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
sequence
|
|
306
|
+
.entries()
|
|
307
|
+
.filter_map(|entry| entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())))
|
|
308
|
+
.collect()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
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)?;
|
|
314
|
+
|
|
315
|
+
let scalar_token =
|
|
316
|
+
find_scalar_token(¤t_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
317
|
+
|
|
318
|
+
let new_text = format_scalar_value(value, scalar_token.kind());
|
|
319
|
+
|
|
320
|
+
self.replace_token(&scalar_token, &new_text)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
pub fn append(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
324
|
+
self.insert_into(dot_path, value, InsertPosition::Last)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
328
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
329
|
+
|
|
330
|
+
if let Ok(current_node) = self.navigate_to_path(&keys) {
|
|
331
|
+
if current_node.descendants().find_map(BlockSeq::cast).is_some() {
|
|
332
|
+
return self.insert_sequence_item(dot_path, value, position);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let (parent_path, key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
|
|
337
|
+
|
|
338
|
+
self.insert_map_key(parent_path, key, value, position)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
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)?;
|
|
344
|
+
|
|
345
|
+
let sequence = current_node
|
|
346
|
+
.descendants()
|
|
347
|
+
.find_map(BlockSeq::cast)
|
|
348
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
349
|
+
|
|
350
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
351
|
+
|
|
352
|
+
if entries.is_empty() {
|
|
353
|
+
return Err(YerbaError::PathNotFound(dot_path.to_string()));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let indent = entries
|
|
357
|
+
.get(1)
|
|
358
|
+
.or(entries.first())
|
|
359
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
360
|
+
.unwrap_or_default();
|
|
361
|
+
|
|
362
|
+
let new_item = format!("- {}", value);
|
|
363
|
+
|
|
364
|
+
match position {
|
|
365
|
+
InsertPosition::Last => {
|
|
366
|
+
let last_entry = entries.last().unwrap();
|
|
367
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
368
|
+
|
|
369
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
InsertPosition::At(index) => {
|
|
373
|
+
if index >= entries.len() {
|
|
374
|
+
let last_entry = entries.last().unwrap();
|
|
375
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
376
|
+
|
|
377
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
378
|
+
} else {
|
|
379
|
+
let target_entry = &entries[index];
|
|
380
|
+
let target_range = target_entry.syntax().text_range();
|
|
381
|
+
let replacement = format!("{}\n{}", new_item, indent);
|
|
382
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
383
|
+
|
|
384
|
+
self.apply_edit(insert_range, &replacement)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
InsertPosition::Before(target_value) => {
|
|
389
|
+
let target_entry = entries
|
|
390
|
+
.iter()
|
|
391
|
+
.find(|entry| {
|
|
392
|
+
entry
|
|
393
|
+
.flow()
|
|
394
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
395
|
+
.map(|text| text == target_value)
|
|
396
|
+
.unwrap_or(false)
|
|
397
|
+
})
|
|
398
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{} item '{}'", dot_path, target_value)))?;
|
|
399
|
+
|
|
400
|
+
let target_range = target_entry.syntax().text_range();
|
|
401
|
+
let replacement = format!("{}\n{}", new_item, indent);
|
|
402
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
403
|
+
|
|
404
|
+
self.apply_edit(insert_range, &replacement)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
InsertPosition::After(target_value) => {
|
|
408
|
+
let target_entry = entries
|
|
409
|
+
.iter()
|
|
410
|
+
.find(|entry| {
|
|
411
|
+
entry
|
|
412
|
+
.flow()
|
|
413
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
414
|
+
.map(|text| text == target_value)
|
|
415
|
+
.unwrap_or(false)
|
|
416
|
+
})
|
|
417
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{} item '{}'", dot_path, target_value)))?;
|
|
418
|
+
|
|
419
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
420
|
+
|
|
421
|
+
self.insert_after_node(target_entry.syntax(), &new_text)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
InsertPosition::FromSortOrder(_) => {
|
|
425
|
+
let last_entry = entries.last().unwrap();
|
|
426
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
427
|
+
|
|
428
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fn insert_map_key(
|
|
434
|
+
&mut self,
|
|
435
|
+
dot_path: &str,
|
|
436
|
+
key: &str,
|
|
437
|
+
value: &str,
|
|
438
|
+
position: InsertPosition,
|
|
439
|
+
) -> Result<(), YerbaError> {
|
|
440
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
441
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
442
|
+
|
|
443
|
+
let map = current_node
|
|
444
|
+
.descendants()
|
|
445
|
+
.find_map(BlockMap::cast)
|
|
446
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
447
|
+
|
|
448
|
+
let entries: Vec<_> = map.entries().collect();
|
|
449
|
+
|
|
450
|
+
if entries.is_empty() {
|
|
451
|
+
let indent = preceding_whitespace_indent(map.syntax());
|
|
452
|
+
let new_entry = format!("\n{}{}: {}", indent, key, value);
|
|
453
|
+
|
|
454
|
+
return self.insert_after_node(map.syntax(), &new_entry);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if find_entry_by_key(&map, key).is_some() {
|
|
458
|
+
return Err(YerbaError::ParseError(format!(
|
|
459
|
+
"key '{}' already exists at '{}'",
|
|
460
|
+
key, dot_path
|
|
461
|
+
)));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let indent = entries
|
|
465
|
+
.get(1)
|
|
466
|
+
.or(entries.first())
|
|
467
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
468
|
+
.unwrap_or_default();
|
|
469
|
+
|
|
470
|
+
let new_entry_text = format!("{}: {}", key, value);
|
|
471
|
+
|
|
472
|
+
match position {
|
|
473
|
+
InsertPosition::Last => {
|
|
474
|
+
let last_entry = entries.last().unwrap();
|
|
475
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
476
|
+
|
|
477
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
InsertPosition::At(index) => {
|
|
481
|
+
if index >= entries.len() {
|
|
482
|
+
let last_entry = entries.last().unwrap();
|
|
483
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
484
|
+
|
|
485
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
486
|
+
} else {
|
|
487
|
+
let target_entry = &entries[index];
|
|
488
|
+
let target_range = target_entry.syntax().text_range();
|
|
489
|
+
let replacement = format!("{}\n{}", new_entry_text, indent);
|
|
490
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
491
|
+
|
|
492
|
+
self.apply_edit(insert_range, &replacement)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
InsertPosition::Before(target_key) => {
|
|
497
|
+
let target_entry = find_entry_by_key(&map, &target_key)
|
|
498
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{}.{}", dot_path, target_key)))?;
|
|
499
|
+
|
|
500
|
+
let target_range = target_entry.syntax().text_range();
|
|
501
|
+
let replacement = format!("{}\n{}", new_entry_text, indent);
|
|
502
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
503
|
+
|
|
504
|
+
self.apply_edit(insert_range, &replacement)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
InsertPosition::After(target_key) => {
|
|
508
|
+
let target_entry = find_entry_by_key(&map, &target_key)
|
|
509
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{}.{}", dot_path, target_key)))?;
|
|
510
|
+
|
|
511
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
512
|
+
|
|
513
|
+
self.insert_after_node(target_entry.syntax(), &new_text)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
InsertPosition::FromSortOrder(order) => {
|
|
517
|
+
let new_key_position = order.iter().position(|ordered_key| ordered_key == key);
|
|
518
|
+
|
|
519
|
+
let resolved = match new_key_position {
|
|
520
|
+
Some(new_position) => {
|
|
521
|
+
let mut insert_after: Option<String> = None;
|
|
522
|
+
|
|
523
|
+
for ordered_key in order.iter().take(new_position).rev() {
|
|
524
|
+
if find_entry_by_key(&map, ordered_key).is_some() {
|
|
525
|
+
insert_after = Some(ordered_key.clone());
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
match insert_after {
|
|
531
|
+
Some(after_key) => InsertPosition::After(after_key),
|
|
532
|
+
None => InsertPosition::At(0),
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
None => InsertPosition::Last,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
self.insert_map_key(dot_path, key, value, resolved)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
pub fn rename(&mut self, source_path: &str, destination_path: &str) -> Result<(), YerbaError> {
|
|
545
|
+
let source_parent = source_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
546
|
+
|
|
547
|
+
let destination_parent = destination_path
|
|
548
|
+
.rsplit_once('.')
|
|
549
|
+
.map(|(parent, _)| parent)
|
|
550
|
+
.unwrap_or("");
|
|
551
|
+
|
|
552
|
+
let destination_key = destination_path
|
|
553
|
+
.rsplit_once('.')
|
|
554
|
+
.map(|(_, key)| key)
|
|
555
|
+
.unwrap_or(destination_path);
|
|
556
|
+
|
|
557
|
+
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();
|
|
561
|
+
|
|
562
|
+
let map = parent_node
|
|
563
|
+
.descendants()
|
|
564
|
+
.find_map(BlockMap::cast)
|
|
565
|
+
.ok_or_else(|| YerbaError::PathNotFound(source_path.to_string()))?;
|
|
566
|
+
|
|
567
|
+
let entry =
|
|
568
|
+
find_entry_by_key(&map, source_key).ok_or_else(|| YerbaError::PathNotFound(source_path.to_string()))?;
|
|
569
|
+
|
|
570
|
+
let key_node = entry
|
|
571
|
+
.key()
|
|
572
|
+
.ok_or_else(|| YerbaError::PathNotFound(source_path.to_string()))?;
|
|
573
|
+
|
|
574
|
+
let key_token =
|
|
575
|
+
find_scalar_token(key_node.syntax()).ok_or_else(|| YerbaError::PathNotFound(source_path.to_string()))?;
|
|
576
|
+
|
|
577
|
+
let new_text = format_scalar_value(destination_key, key_token.kind());
|
|
578
|
+
|
|
579
|
+
self.replace_token(&key_token, &new_text)
|
|
580
|
+
} else {
|
|
581
|
+
let value = self
|
|
582
|
+
.get(source_path)
|
|
583
|
+
.ok_or_else(|| YerbaError::PathNotFound(source_path.to_string()))?;
|
|
584
|
+
|
|
585
|
+
self.delete(source_path)?;
|
|
586
|
+
self.insert_into(destination_path, &value, InsertPosition::Last)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
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();
|
|
594
|
+
|
|
595
|
+
let map = parent_node
|
|
596
|
+
.descendants()
|
|
597
|
+
.find_map(BlockMap::cast)
|
|
598
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
599
|
+
|
|
600
|
+
let entry = find_entry_by_key(&map, last_key).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
601
|
+
|
|
602
|
+
self.remove_node(entry.syntax())
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
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)?;
|
|
608
|
+
|
|
609
|
+
let sequence = current_node
|
|
610
|
+
.descendants()
|
|
611
|
+
.find_map(BlockSeq::cast)
|
|
612
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
613
|
+
|
|
614
|
+
let target_entry = sequence
|
|
615
|
+
.entries()
|
|
616
|
+
.find(|entry| {
|
|
617
|
+
entry
|
|
618
|
+
.flow()
|
|
619
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
620
|
+
.map(|text| text == value)
|
|
621
|
+
.unwrap_or(false)
|
|
622
|
+
})
|
|
623
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{} item '{}'", dot_path, value)))?;
|
|
624
|
+
|
|
625
|
+
self.remove_node(target_entry.syntax())
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
pub fn move_item(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
|
|
629
|
+
if from == to {
|
|
630
|
+
return Ok(());
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
634
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
635
|
+
|
|
636
|
+
let sequence = current_node
|
|
637
|
+
.descendants()
|
|
638
|
+
.find_map(BlockSeq::cast)
|
|
639
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
640
|
+
|
|
641
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
642
|
+
|
|
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
|
+
)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
pub fn move_key(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
|
|
654
|
+
if from == to {
|
|
655
|
+
return Ok(());
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
659
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
660
|
+
|
|
661
|
+
let map = current_node
|
|
662
|
+
.descendants()
|
|
663
|
+
.find_map(BlockMap::cast)
|
|
664
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
665
|
+
|
|
666
|
+
let entries: Vec<_> = map.entries().collect();
|
|
667
|
+
|
|
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
|
+
)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
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)?;
|
|
681
|
+
|
|
682
|
+
let map = current_node
|
|
683
|
+
.descendants()
|
|
684
|
+
.find_map(BlockMap::cast)
|
|
685
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
686
|
+
|
|
687
|
+
if let Ok(index) = reference.parse::<usize>() {
|
|
688
|
+
let length = map.entries().count();
|
|
689
|
+
|
|
690
|
+
if index >= length {
|
|
691
|
+
return Err(YerbaError::IndexOutOfBounds(index, length));
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return Ok(index);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
map
|
|
698
|
+
.entries()
|
|
699
|
+
.enumerate()
|
|
700
|
+
.find(|(_index, entry)| {
|
|
701
|
+
entry
|
|
702
|
+
.key()
|
|
703
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
704
|
+
.map(|key_text| key_text == reference)
|
|
705
|
+
.unwrap_or(false)
|
|
706
|
+
})
|
|
707
|
+
.map(|(index, _entry)| index)
|
|
708
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{} key '{}'", dot_path, reference)))
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
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)?;
|
|
714
|
+
|
|
715
|
+
let sequence = current_node
|
|
716
|
+
.descendants()
|
|
717
|
+
.find_map(BlockSeq::cast)
|
|
718
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
719
|
+
|
|
720
|
+
if let Ok(index) = reference.parse::<usize>() {
|
|
721
|
+
let length = sequence.entries().count();
|
|
722
|
+
|
|
723
|
+
if index >= length {
|
|
724
|
+
return Err(YerbaError::IndexOutOfBounds(index, length));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return Ok(index);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
sequence
|
|
731
|
+
.entries()
|
|
732
|
+
.enumerate()
|
|
733
|
+
.find(|(_index, entry)| {
|
|
734
|
+
entry
|
|
735
|
+
.flow()
|
|
736
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
737
|
+
.map(|text| text == reference)
|
|
738
|
+
.unwrap_or(false)
|
|
739
|
+
})
|
|
740
|
+
.map(|(index, _entry)| index)
|
|
741
|
+
.ok_or_else(|| YerbaError::PathNotFound(format!("{} item '{}'", dot_path, reference)))
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
pub fn validate_sort_keys(&self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
745
|
+
if dot_path == "[]" || dot_path.ends_with(".[]") {
|
|
746
|
+
let seq_path = if dot_path == "[]" {
|
|
747
|
+
""
|
|
748
|
+
} else {
|
|
749
|
+
&dot_path[..dot_path.len() - 3]
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
return self.validate_each_sort_keys(seq_path, key_order);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
756
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
757
|
+
|
|
758
|
+
let map = current_node
|
|
759
|
+
.descendants()
|
|
760
|
+
.find_map(BlockMap::cast)
|
|
761
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
762
|
+
|
|
763
|
+
let unknown_keys: Vec<String> = map
|
|
764
|
+
.entries()
|
|
765
|
+
.filter_map(|entry| entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())))
|
|
766
|
+
.filter(|key_name| !key_order.contains(&key_name.as_str()))
|
|
767
|
+
.collect();
|
|
768
|
+
|
|
769
|
+
if unknown_keys.is_empty() {
|
|
770
|
+
Ok(())
|
|
771
|
+
} else {
|
|
772
|
+
Err(YerbaError::UnknownKeys(unknown_keys))
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
pub fn sort_keys(&mut self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
777
|
+
if dot_path == "[]" || dot_path.ends_with(".[]") {
|
|
778
|
+
let seq_path = if dot_path == "[]" {
|
|
779
|
+
""
|
|
780
|
+
} else {
|
|
781
|
+
&dot_path[..dot_path.len() - 3]
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
return self.sort_each_keys(seq_path, key_order);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
788
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
789
|
+
|
|
790
|
+
let map = current_node
|
|
791
|
+
.descendants()
|
|
792
|
+
.find_map(BlockMap::cast)
|
|
793
|
+
.ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
|
|
794
|
+
|
|
795
|
+
let entries: Vec<_> = map.entries().collect();
|
|
796
|
+
|
|
797
|
+
if entries.len() <= 1 {
|
|
798
|
+
return Ok(());
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
let entry_data: Vec<(String, String)> = entries
|
|
802
|
+
.iter()
|
|
803
|
+
.map(|entry| {
|
|
804
|
+
let key_name = entry
|
|
805
|
+
.key()
|
|
806
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
807
|
+
.unwrap_or_default();
|
|
808
|
+
let text = entry.syntax().text().to_string();
|
|
809
|
+
(key_name, text)
|
|
810
|
+
})
|
|
811
|
+
.collect();
|
|
812
|
+
|
|
813
|
+
let mut sorted = entry_data.clone();
|
|
814
|
+
|
|
815
|
+
sorted.sort_by(|(key_a, _), (key_b, _)| {
|
|
816
|
+
let position_a = key_order.iter().position(|&key| key == key_a);
|
|
817
|
+
let position_b = key_order.iter().position(|&key| key == key_b);
|
|
818
|
+
|
|
819
|
+
match (position_a, position_b) {
|
|
820
|
+
(Some(a), Some(b)) => a.cmp(&b),
|
|
821
|
+
(Some(_), None) => std::cmp::Ordering::Less,
|
|
822
|
+
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
823
|
+
(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();
|
|
826
|
+
original_a.cmp(&original_b)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
|
|
832
|
+
== entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
|
|
833
|
+
{
|
|
834
|
+
return Ok(());
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
let indent = entries
|
|
838
|
+
.get(1)
|
|
839
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
840
|
+
.unwrap_or_default();
|
|
841
|
+
|
|
842
|
+
let map_text = rebuild_entries(sorted.iter().map(|(_key, text)| text.as_str()), &indent);
|
|
843
|
+
let map_range = map.syntax().text_range();
|
|
844
|
+
|
|
845
|
+
self.apply_edit(map_range, &map_text)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
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)?;
|
|
851
|
+
|
|
852
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
853
|
+
Some(sequence) => sequence,
|
|
854
|
+
None => return Ok(()),
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
858
|
+
|
|
859
|
+
for entry in sequence.entries() {
|
|
860
|
+
let entry_node = entry.syntax();
|
|
861
|
+
|
|
862
|
+
let map = match entry_node.descendants().find_map(BlockMap::cast) {
|
|
863
|
+
Some(map) => map,
|
|
864
|
+
None => continue,
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
let entries: Vec<_> = map.entries().collect();
|
|
868
|
+
|
|
869
|
+
if entries.len() <= 1 {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
let entry_data: Vec<(String, String)> = entries
|
|
874
|
+
.iter()
|
|
875
|
+
.map(|entry| {
|
|
876
|
+
let key_name = entry
|
|
877
|
+
.key()
|
|
878
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
879
|
+
.unwrap_or_default();
|
|
880
|
+
let text = entry.syntax().text().to_string();
|
|
881
|
+
(key_name, text)
|
|
882
|
+
})
|
|
883
|
+
.collect();
|
|
884
|
+
|
|
885
|
+
let mut sorted = entry_data.clone();
|
|
886
|
+
|
|
887
|
+
sorted.sort_by(|(key_a, _), (key_b, _)| {
|
|
888
|
+
let position_a = key_order.iter().position(|&key| key == key_a);
|
|
889
|
+
let position_b = key_order.iter().position(|&key| key == key_b);
|
|
890
|
+
|
|
891
|
+
match (position_a, position_b) {
|
|
892
|
+
(Some(a), Some(b)) => a.cmp(&b),
|
|
893
|
+
(Some(_), None) => std::cmp::Ordering::Less,
|
|
894
|
+
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
895
|
+
|
|
896
|
+
(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();
|
|
899
|
+
|
|
900
|
+
original_a.cmp(&original_b)
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
|
|
906
|
+
== entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
|
|
907
|
+
{
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
let indent = entries
|
|
912
|
+
.get(1)
|
|
913
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
914
|
+
.unwrap_or_default();
|
|
915
|
+
|
|
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));
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if edits.is_empty() {
|
|
922
|
+
return Ok(());
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
edits.reverse();
|
|
926
|
+
|
|
927
|
+
let source = self.root.text().to_string();
|
|
928
|
+
let mut new_source = source;
|
|
929
|
+
|
|
930
|
+
for (range, replacement) in edits {
|
|
931
|
+
let start: usize = range.start().into();
|
|
932
|
+
let end: usize = range.end().into();
|
|
933
|
+
|
|
934
|
+
new_source.replace_range(start..end, &replacement);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
let path = self.path.take();
|
|
938
|
+
*self = Self::parse(&new_source)?;
|
|
939
|
+
self.path = path;
|
|
940
|
+
|
|
941
|
+
Ok(())
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
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)?;
|
|
947
|
+
|
|
948
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
949
|
+
Some(sequence) => sequence,
|
|
950
|
+
None => return Ok(()),
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
let mut all_unknown: Vec<String> = Vec::new();
|
|
954
|
+
|
|
955
|
+
for entry in sequence.entries() {
|
|
956
|
+
if let Some(map) = entry.syntax().descendants().find_map(BlockMap::cast) {
|
|
957
|
+
for map_entry in map.entries() {
|
|
958
|
+
if let Some(key_name) = map_entry
|
|
959
|
+
.key()
|
|
960
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
961
|
+
{
|
|
962
|
+
if !key_order.contains(&key_name.as_str()) && !all_unknown.contains(&key_name) {
|
|
963
|
+
all_unknown.push(key_name);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if all_unknown.is_empty() {
|
|
971
|
+
Ok(())
|
|
972
|
+
} else {
|
|
973
|
+
Err(YerbaError::UnknownKeys(all_unknown))
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
pub fn sort_items(&mut self, dot_path: &str, sort_fields: &[SortField]) -> Result<(), YerbaError> {
|
|
978
|
+
if dot_path.contains("[].") {
|
|
979
|
+
return self.sort_each_items(dot_path, sort_fields);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
983
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
984
|
+
|
|
985
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
986
|
+
Some(sequence) => sequence,
|
|
987
|
+
None => return Ok(()),
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
991
|
+
|
|
992
|
+
if entries.len() <= 1 {
|
|
993
|
+
return Ok(());
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
let mut sortable: Vec<(Vec<String>, String)> = entries
|
|
997
|
+
.iter()
|
|
998
|
+
.map(|entry| {
|
|
999
|
+
let sort_values = if sort_fields.is_empty() {
|
|
1000
|
+
vec![entry
|
|
1001
|
+
.flow()
|
|
1002
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
1003
|
+
.unwrap_or_default()]
|
|
1004
|
+
} else {
|
|
1005
|
+
sort_fields
|
|
1006
|
+
.iter()
|
|
1007
|
+
.map(|field| {
|
|
1008
|
+
let nodes = navigate_from_node(entry.syntax(), &field.path);
|
|
1009
|
+
nodes.first().and_then(extract_scalar_text).unwrap_or_default()
|
|
1010
|
+
})
|
|
1011
|
+
.collect()
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
(sort_values, entry.syntax().text().to_string())
|
|
1015
|
+
})
|
|
1016
|
+
.collect();
|
|
1017
|
+
|
|
1018
|
+
let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
|
|
1019
|
+
|
|
1020
|
+
sortable.sort_by(|(values_a, _), (values_b, _)| {
|
|
1021
|
+
for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
|
|
1022
|
+
let value_a = &values_a[index];
|
|
1023
|
+
let value_b = &values_b[index];
|
|
1024
|
+
|
|
1025
|
+
let ordering = value_a.cmp(value_b);
|
|
1026
|
+
|
|
1027
|
+
let ordering = if field.ascending { ordering } else { ordering.reverse() };
|
|
1028
|
+
|
|
1029
|
+
if ordering != std::cmp::Ordering::Equal {
|
|
1030
|
+
return ordering;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
|
|
1035
|
+
return values_a[0].cmp(&values_b[0]);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
std::cmp::Ordering::Equal
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
|
|
1042
|
+
|
|
1043
|
+
if sorted_texts == original_order {
|
|
1044
|
+
return Ok(());
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
let indent = entries
|
|
1048
|
+
.get(1)
|
|
1049
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
1050
|
+
.unwrap_or_default();
|
|
1051
|
+
|
|
1052
|
+
let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
|
|
1053
|
+
let sequence_range = sequence.syntax().text_range();
|
|
1054
|
+
|
|
1055
|
+
self.apply_edit(sequence_range, &sequence_text)
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
fn sort_each_items(&mut self, dot_path: &str, sort_fields: &[SortField]) -> Result<(), YerbaError> {
|
|
1059
|
+
let (parent_path, child_path) = if let Some(last_bracket) = dot_path.rfind("[].") {
|
|
1060
|
+
(&dot_path[..last_bracket + 2], &dot_path[last_bracket + 3..])
|
|
1061
|
+
} else {
|
|
1062
|
+
(dot_path, "")
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
let parent_nodes = self.navigate_to_many(parent_path);
|
|
1066
|
+
let source = self.root.text().to_string();
|
|
1067
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
1068
|
+
|
|
1069
|
+
for parent_node in &parent_nodes {
|
|
1070
|
+
let child_nodes = if child_path.is_empty() {
|
|
1071
|
+
vec![parent_node.clone()]
|
|
1072
|
+
} else {
|
|
1073
|
+
navigate_from_node(parent_node, child_path)
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
for child_node in &child_nodes {
|
|
1077
|
+
let sequence = match child_node.descendants().find_map(BlockSeq::cast) {
|
|
1078
|
+
Some(sequence) => sequence,
|
|
1079
|
+
None => continue,
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
1083
|
+
|
|
1084
|
+
if entries.len() <= 1 {
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
let mut sortable: Vec<(Vec<String>, String)> = entries
|
|
1089
|
+
.iter()
|
|
1090
|
+
.map(|entry| {
|
|
1091
|
+
let sort_values = if sort_fields.is_empty() {
|
|
1092
|
+
vec![entry
|
|
1093
|
+
.flow()
|
|
1094
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
1095
|
+
.unwrap_or_default()]
|
|
1096
|
+
} else {
|
|
1097
|
+
sort_fields
|
|
1098
|
+
.iter()
|
|
1099
|
+
.map(|field| {
|
|
1100
|
+
let nodes = navigate_from_node(entry.syntax(), &field.path);
|
|
1101
|
+
nodes.first().and_then(extract_scalar_text).unwrap_or_default()
|
|
1102
|
+
})
|
|
1103
|
+
.collect()
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
(sort_values, entry.syntax().text().to_string())
|
|
1107
|
+
})
|
|
1108
|
+
.collect();
|
|
1109
|
+
|
|
1110
|
+
let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
|
|
1111
|
+
|
|
1112
|
+
sortable.sort_by(|(values_a, _), (values_b, _)| {
|
|
1113
|
+
for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
|
|
1114
|
+
let value_a = &values_a[index];
|
|
1115
|
+
let value_b = &values_b[index];
|
|
1116
|
+
|
|
1117
|
+
let ordering = value_a.cmp(value_b);
|
|
1118
|
+
let ordering = if field.ascending { ordering } else { ordering.reverse() };
|
|
1119
|
+
|
|
1120
|
+
if ordering != std::cmp::Ordering::Equal {
|
|
1121
|
+
return ordering;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
|
|
1126
|
+
return values_a[0].cmp(&values_b[0]);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
std::cmp::Ordering::Equal
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
|
|
1133
|
+
|
|
1134
|
+
if sorted_texts == original_order {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
let indent = entries
|
|
1139
|
+
.get(1)
|
|
1140
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
1141
|
+
.unwrap_or_default();
|
|
1142
|
+
|
|
1143
|
+
let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
|
|
1144
|
+
|
|
1145
|
+
edits.push((sequence.syntax().text_range(), sequence_text));
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if edits.is_empty() {
|
|
1150
|
+
return Ok(());
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
edits.reverse();
|
|
1154
|
+
|
|
1155
|
+
let mut new_source = source;
|
|
1156
|
+
|
|
1157
|
+
for (range, replacement) in edits {
|
|
1158
|
+
let start: usize = range.start().into();
|
|
1159
|
+
let end: usize = range.end().into();
|
|
1160
|
+
|
|
1161
|
+
new_source.replace_range(start..end, &replacement);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
let path = self.path.take();
|
|
1165
|
+
*self = Self::parse(&new_source)?;
|
|
1166
|
+
self.path = path;
|
|
1167
|
+
|
|
1168
|
+
Ok(())
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
|
|
1172
|
+
let keys: Vec<&str> = dot_path.split('.').collect();
|
|
1173
|
+
let current_node = self.navigate_to_path(&keys)?;
|
|
1174
|
+
|
|
1175
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
1176
|
+
Some(sequence) => sequence,
|
|
1177
|
+
None => return Ok(()),
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
1181
|
+
|
|
1182
|
+
if entries.len() <= 1 {
|
|
1183
|
+
return Ok(());
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
let source = self.root.text().to_string();
|
|
1187
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
1188
|
+
|
|
1189
|
+
for entry in entries.iter().skip(1) {
|
|
1190
|
+
if let Some(whitespace_token) = preceding_whitespace_token(entry.syntax()) {
|
|
1191
|
+
let whitespace_text = whitespace_token.text();
|
|
1192
|
+
|
|
1193
|
+
let newline_count = whitespace_text.chars().filter(|character| *character == '\n').count();
|
|
1194
|
+
|
|
1195
|
+
let indent = whitespace_text
|
|
1196
|
+
.rfind('\n')
|
|
1197
|
+
.map(|position| &whitespace_text[position + 1..])
|
|
1198
|
+
.unwrap_or("");
|
|
1199
|
+
|
|
1200
|
+
let desired_newlines = blank_lines + 1;
|
|
1201
|
+
|
|
1202
|
+
if newline_count != desired_newlines {
|
|
1203
|
+
let new_whitespace = format!("{}{}", "\n".repeat(desired_newlines), indent);
|
|
1204
|
+
|
|
1205
|
+
edits.push((whitespace_token.text_range(), new_whitespace));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if edits.is_empty() {
|
|
1211
|
+
return Ok(());
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
edits.reverse();
|
|
1215
|
+
|
|
1216
|
+
let mut new_source = source;
|
|
1217
|
+
|
|
1218
|
+
for (range, replacement) in edits {
|
|
1219
|
+
let start: usize = range.start().into();
|
|
1220
|
+
let end: usize = range.end().into();
|
|
1221
|
+
|
|
1222
|
+
new_source.replace_range(start..end, &replacement);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
let path = self.path.take();
|
|
1226
|
+
*self = Self::parse(&new_source)?;
|
|
1227
|
+
self.path = path;
|
|
1228
|
+
|
|
1229
|
+
Ok(())
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
pub fn enforce_key_style(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
|
|
1233
|
+
let source = self.root.text().to_string();
|
|
1234
|
+
|
|
1235
|
+
let scope_node = match dot_path {
|
|
1236
|
+
Some(path) if !path.is_empty() => {
|
|
1237
|
+
let keys: Vec<&str> = path.split('.').collect();
|
|
1238
|
+
self.navigate_to_path(&keys)?
|
|
1239
|
+
}
|
|
1240
|
+
_ => self.root.clone(),
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
let scope_range = scope_node.text_range();
|
|
1244
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
1245
|
+
|
|
1246
|
+
for element in self.root.descendants_with_tokens() {
|
|
1247
|
+
if let Some(token) = element.into_token() {
|
|
1248
|
+
if !scope_range.contains_range(token.text_range()) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if !is_map_key(&token) {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
let current_kind = token.kind();
|
|
1257
|
+
|
|
1258
|
+
if !matches!(
|
|
1259
|
+
current_kind,
|
|
1260
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
1261
|
+
) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
let target_kind = style.to_syntax_kind();
|
|
1266
|
+
|
|
1267
|
+
if current_kind == target_kind {
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
let raw_value = match current_kind {
|
|
1272
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
1273
|
+
let text = token.text();
|
|
1274
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
1278
|
+
let text = token.text();
|
|
1279
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
1283
|
+
|
|
1284
|
+
_ => continue,
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
let new_text = match style {
|
|
1288
|
+
QuoteStyle::Double => {
|
|
1289
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
1290
|
+
format!("\"{}\"", escaped)
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
QuoteStyle::Single => {
|
|
1294
|
+
let escaped = raw_value.replace('\'', "''");
|
|
1295
|
+
format!("'{}'", escaped)
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
QuoteStyle::Plain => raw_value,
|
|
1299
|
+
|
|
1300
|
+
_ => continue,
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
if new_text != token.text() {
|
|
1304
|
+
edits.push((token.text_range(), new_text));
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if edits.is_empty() {
|
|
1310
|
+
return Ok(());
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
edits.reverse();
|
|
1314
|
+
|
|
1315
|
+
let mut new_source = source;
|
|
1316
|
+
|
|
1317
|
+
for (range, replacement) in edits {
|
|
1318
|
+
let start: usize = range.start().into();
|
|
1319
|
+
let end: usize = range.end().into();
|
|
1320
|
+
|
|
1321
|
+
new_source.replace_range(start..end, &replacement);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
let path = self.path.take();
|
|
1325
|
+
*self = Self::parse(&new_source)?;
|
|
1326
|
+
self.path = path;
|
|
1327
|
+
|
|
1328
|
+
Ok(())
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
pub fn enforce_quotes(&mut self, style: &QuoteStyle) -> Result<(), YerbaError> {
|
|
1332
|
+
self.enforce_quotes_at(style, None)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
pub fn enforce_quotes_at(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
|
|
1336
|
+
let source = self.root.text().to_string();
|
|
1337
|
+
|
|
1338
|
+
let scope_node = match dot_path {
|
|
1339
|
+
Some(path) if !path.is_empty() => {
|
|
1340
|
+
let keys: Vec<&str> = path.split('.').collect();
|
|
1341
|
+
self.navigate_to_path(&keys)?
|
|
1342
|
+
}
|
|
1343
|
+
_ => self.root.clone(),
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
let scope_range = scope_node.text_range();
|
|
1347
|
+
|
|
1348
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
1349
|
+
|
|
1350
|
+
for element in self.root.descendants_with_tokens() {
|
|
1351
|
+
if let Some(token) = element.into_token() {
|
|
1352
|
+
if !scope_range.contains_range(token.text_range()) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
if is_map_key(&token) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
let current_kind = token.kind();
|
|
1361
|
+
|
|
1362
|
+
if !matches!(
|
|
1363
|
+
current_kind,
|
|
1364
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
1365
|
+
) {
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
let target_kind = style.to_syntax_kind();
|
|
1370
|
+
|
|
1371
|
+
if current_kind == target_kind {
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
let raw_value = match current_kind {
|
|
1376
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
1377
|
+
let text = token.text();
|
|
1378
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
1382
|
+
let text = token.text();
|
|
1383
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
1387
|
+
|
|
1388
|
+
_ => continue,
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
if is_yaml_non_string(&raw_value) {
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
let new_text = match style {
|
|
1396
|
+
QuoteStyle::Double => {
|
|
1397
|
+
if raw_value.contains('"') && current_kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
1402
|
+
format!("\"{}\"", escaped)
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
QuoteStyle::Single => {
|
|
1406
|
+
let escaped = raw_value.replace('\'', "''");
|
|
1407
|
+
format!("'{}'", escaped)
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
QuoteStyle::Plain => {
|
|
1411
|
+
if raw_value.contains('"') || raw_value.contains('\'') || raw_value.contains(':') || raw_value.contains('#')
|
|
1412
|
+
{
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
raw_value
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
_ => continue,
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
if new_text != token.text() {
|
|
1423
|
+
edits.push((token.text_range(), new_text));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if edits.is_empty() {
|
|
1429
|
+
return Ok(());
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
edits.reverse();
|
|
1433
|
+
|
|
1434
|
+
let mut new_source = source;
|
|
1435
|
+
|
|
1436
|
+
for (range, replacement) in edits {
|
|
1437
|
+
let start: usize = range.start().into();
|
|
1438
|
+
let end: usize = range.end().into();
|
|
1439
|
+
|
|
1440
|
+
new_source.replace_range(start..end, &replacement);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
let path = self.path.take();
|
|
1444
|
+
*self = Self::parse(&new_source)?;
|
|
1445
|
+
self.path = path;
|
|
1446
|
+
|
|
1447
|
+
Ok(())
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
pub fn save(&self) -> Result<(), YerbaError> {
|
|
1451
|
+
let path = self.path.as_ref().ok_or_else(|| {
|
|
1452
|
+
YerbaError::IoError(std::io::Error::new(
|
|
1453
|
+
std::io::ErrorKind::NotFound,
|
|
1454
|
+
"no file path associated with this document",
|
|
1455
|
+
))
|
|
1456
|
+
})?;
|
|
1457
|
+
|
|
1458
|
+
fs::write(path, self.to_string())?;
|
|
1459
|
+
|
|
1460
|
+
Ok(())
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
pub fn save_to(&self, path: impl AsRef<Path>) -> Result<(), YerbaError> {
|
|
1464
|
+
fs::write(path, self.to_string())?;
|
|
1465
|
+
|
|
1466
|
+
Ok(())
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
pub fn navigate_to_many(&self, dot_path: &str) -> Vec<SyntaxNode> {
|
|
1470
|
+
let segments = parse_path_segments(dot_path);
|
|
1471
|
+
|
|
1472
|
+
let root = match Root::cast(self.root.clone()) {
|
|
1473
|
+
Some(root) => root,
|
|
1474
|
+
None => return Vec::new(),
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
let document = match root.documents().next() {
|
|
1478
|
+
Some(document) => document,
|
|
1479
|
+
None => return Vec::new(),
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
let mut current_nodes = vec![document.syntax().clone()];
|
|
1483
|
+
|
|
1484
|
+
if segments.is_empty() {
|
|
1485
|
+
if let Some(sequence) = document.syntax().descendants().find_map(BlockSeq::cast) {
|
|
1486
|
+
current_nodes = sequence.entries().map(|entry| entry.syntax().clone()).collect();
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
return current_nodes;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
for segment in &segments {
|
|
1493
|
+
let mut next_nodes = Vec::new();
|
|
1494
|
+
|
|
1495
|
+
for node in ¤t_nodes {
|
|
1496
|
+
next_nodes.extend(resolve_segment(node, segment));
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
current_nodes = next_nodes;
|
|
1500
|
+
|
|
1501
|
+
if current_nodes.is_empty() {
|
|
1502
|
+
break;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
current_nodes
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
fn navigate_to_path(&self, keys: &[&str]) -> Result<SyntaxNode, YerbaError> {
|
|
1510
|
+
let keys: Vec<&&str> = keys.iter().filter(|key| !key.is_empty()).collect();
|
|
1511
|
+
let path_string = keys.iter().map(|key| **key).collect::<Vec<_>>().join(".");
|
|
1512
|
+
|
|
1513
|
+
let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
|
|
1514
|
+
|
|
1515
|
+
let document = root
|
|
1516
|
+
.documents()
|
|
1517
|
+
.next()
|
|
1518
|
+
.ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
|
|
1519
|
+
|
|
1520
|
+
let mut current_node = document.syntax().clone();
|
|
1521
|
+
|
|
1522
|
+
for key in &keys {
|
|
1523
|
+
let map = current_node
|
|
1524
|
+
.descendants()
|
|
1525
|
+
.find_map(BlockMap::cast)
|
|
1526
|
+
.ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
|
|
1527
|
+
|
|
1528
|
+
let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
|
|
1529
|
+
|
|
1530
|
+
let map_value = entry
|
|
1531
|
+
.value()
|
|
1532
|
+
.ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
|
|
1533
|
+
|
|
1534
|
+
current_node = map_value.syntax().clone();
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
Ok(current_node)
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
fn replace_token(&mut self, token: &SyntaxToken, new_text: &str) -> Result<(), YerbaError> {
|
|
1541
|
+
let range = token.text_range();
|
|
1542
|
+
|
|
1543
|
+
self.apply_edit(range, new_text)
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
fn insert_after_node(&mut self, node: &SyntaxNode, text: &str) -> Result<(), YerbaError> {
|
|
1547
|
+
let position = node.text_range().end();
|
|
1548
|
+
let range = TextRange::new(position, position);
|
|
1549
|
+
|
|
1550
|
+
self.apply_edit(range, text)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
fn remove_node(&mut self, node: &SyntaxNode) -> Result<(), YerbaError> {
|
|
1554
|
+
let range = removal_range(node);
|
|
1555
|
+
|
|
1556
|
+
self.apply_edit(range, "")
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
fn reorder_entries<T>(
|
|
1560
|
+
&mut self,
|
|
1561
|
+
entries: &[T],
|
|
1562
|
+
from: usize,
|
|
1563
|
+
to: usize,
|
|
1564
|
+
get_text: impl Fn(&T) -> String,
|
|
1565
|
+
get_indent: impl Fn(&T) -> String,
|
|
1566
|
+
range: TextRange,
|
|
1567
|
+
) -> Result<(), YerbaError>
|
|
1568
|
+
where
|
|
1569
|
+
T: rowan::ast::AstNode,
|
|
1570
|
+
{
|
|
1571
|
+
let length = entries.len();
|
|
1572
|
+
|
|
1573
|
+
if from >= length {
|
|
1574
|
+
return Err(YerbaError::IndexOutOfBounds(from, length));
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
if to >= length {
|
|
1578
|
+
return Err(YerbaError::IndexOutOfBounds(to, length));
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let entry_texts: Vec<String> = entries.iter().map(&get_text).collect();
|
|
1582
|
+
|
|
1583
|
+
let mut reordered = entry_texts.clone();
|
|
1584
|
+
let item = reordered.remove(from);
|
|
1585
|
+
|
|
1586
|
+
reordered.insert(to, item);
|
|
1587
|
+
|
|
1588
|
+
let indent = entries.get(1).map(&get_indent).unwrap_or_default();
|
|
1589
|
+
let text = rebuild_entries(reordered.iter().map(|text| text.as_str()), &indent);
|
|
1590
|
+
|
|
1591
|
+
self.apply_edit(range, &text)
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
fn apply_edit(&mut self, range: TextRange, replacement: &str) -> Result<(), YerbaError> {
|
|
1595
|
+
let source = self.root.text().to_string();
|
|
1596
|
+
let start: usize = range.start().into();
|
|
1597
|
+
let end: usize = range.end().into();
|
|
1598
|
+
|
|
1599
|
+
let mut new_source = source;
|
|
1600
|
+
new_source.replace_range(start..end, replacement);
|
|
1601
|
+
|
|
1602
|
+
let path = self.path.take();
|
|
1603
|
+
*self = Self::parse(&new_source)?;
|
|
1604
|
+
self.path = path;
|
|
1605
|
+
|
|
1606
|
+
Ok(())
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
impl std::fmt::Display for Document {
|
|
1611
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
1612
|
+
write!(f, "{}", self.root.text())
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
fn parse_condition(condition: &str) -> Option<(String, &str, String)> {
|
|
1617
|
+
let (left, operator, right) = if let Some(index) = condition.find(" not_contains ") {
|
|
1618
|
+
(
|
|
1619
|
+
condition[..index].trim(),
|
|
1620
|
+
"not_contains",
|
|
1621
|
+
condition[index + 14..].trim(),
|
|
1622
|
+
)
|
|
1623
|
+
} else if let Some(index) = condition.find(" contains ") {
|
|
1624
|
+
(condition[..index].trim(), "contains", condition[index + 10..].trim())
|
|
1625
|
+
} else if let Some(index) = condition.find("!=") {
|
|
1626
|
+
(condition[..index].trim(), "!=", condition[index + 2..].trim())
|
|
1627
|
+
} else if let Some(index) = condition.find("==") {
|
|
1628
|
+
(condition[..index].trim(), "==", condition[index + 2..].trim())
|
|
1629
|
+
} else {
|
|
1630
|
+
return None;
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
let right = right
|
|
1634
|
+
.trim_start_matches('"')
|
|
1635
|
+
.trim_end_matches('"')
|
|
1636
|
+
.trim_start_matches('\'')
|
|
1637
|
+
.trim_end_matches('\'');
|
|
1638
|
+
|
|
1639
|
+
Some((left.to_string(), operator, right.to_string()))
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
fn parse_path_segments(path: &str) -> Vec<&str> {
|
|
1643
|
+
if path.is_empty() {
|
|
1644
|
+
return Vec::new();
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
let mut segments = Vec::new();
|
|
1648
|
+
let mut rest = path;
|
|
1649
|
+
|
|
1650
|
+
while !rest.is_empty() {
|
|
1651
|
+
if rest.starts_with('[') {
|
|
1652
|
+
if let Some(close) = rest.find(']') {
|
|
1653
|
+
segments.push(&rest[..close + 1]);
|
|
1654
|
+
rest = &rest[close + 1..];
|
|
1655
|
+
|
|
1656
|
+
if rest.starts_with('.') {
|
|
1657
|
+
rest = &rest[1..];
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
segments.push(rest);
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
} else {
|
|
1664
|
+
let dot_index = rest.find('.');
|
|
1665
|
+
let bracket_index = rest.find('[');
|
|
1666
|
+
|
|
1667
|
+
let split_at = match (dot_index, bracket_index) {
|
|
1668
|
+
(Some(dot), Some(bracket)) => Some(dot.min(bracket)),
|
|
1669
|
+
(Some(dot), None) => Some(dot),
|
|
1670
|
+
(None, Some(bracket)) => Some(bracket),
|
|
1671
|
+
(None, None) => None,
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
match split_at {
|
|
1675
|
+
Some(index) => {
|
|
1676
|
+
let segment = &rest[..index];
|
|
1677
|
+
|
|
1678
|
+
if !segment.is_empty() {
|
|
1679
|
+
segments.push(segment);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
rest = &rest[index..];
|
|
1683
|
+
|
|
1684
|
+
if rest.starts_with('.') {
|
|
1685
|
+
rest = &rest[1..];
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
None => {
|
|
1690
|
+
segments.push(rest);
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
segments
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
fn parse_bracket_index(segment: &str) -> Option<usize> {
|
|
1701
|
+
if segment == "[]" {
|
|
1702
|
+
return None;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
segment
|
|
1706
|
+
.strip_prefix('[')
|
|
1707
|
+
.and_then(|rest| rest.strip_suffix(']'))
|
|
1708
|
+
.and_then(|inner| inner.parse::<usize>().ok())
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
fn resolve_segment(node: &SyntaxNode, segment: &str) -> Vec<SyntaxNode> {
|
|
1712
|
+
if segment.starts_with('[') {
|
|
1713
|
+
if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
|
|
1714
|
+
match parse_bracket_index(segment) {
|
|
1715
|
+
None => sequence.entries().map(|entry| entry.syntax().clone()).collect(),
|
|
1716
|
+
|
|
1717
|
+
Some(index) => sequence
|
|
1718
|
+
.entries()
|
|
1719
|
+
.nth(index)
|
|
1720
|
+
.map(|entry| vec![entry.syntax().clone()])
|
|
1721
|
+
.unwrap_or_default(),
|
|
1722
|
+
}
|
|
1723
|
+
} else {
|
|
1724
|
+
Vec::new()
|
|
1725
|
+
}
|
|
1726
|
+
} else {
|
|
1727
|
+
if let Some(map) = node.descendants().find_map(BlockMap::cast) {
|
|
1728
|
+
if let Some(entry) = find_entry_by_key(&map, segment) {
|
|
1729
|
+
if let Some(value) = entry.value() {
|
|
1730
|
+
return vec![value.syntax().clone()];
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
Vec::new()
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
|
|
1740
|
+
let segments = parse_path_segments(path);
|
|
1741
|
+
let mut current_nodes = vec![node.clone()];
|
|
1742
|
+
|
|
1743
|
+
for segment in &segments {
|
|
1744
|
+
let mut next_nodes = Vec::new();
|
|
1745
|
+
|
|
1746
|
+
for current in ¤t_nodes {
|
|
1747
|
+
next_nodes.extend(resolve_segment(current, segment));
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
current_nodes = next_nodes;
|
|
1751
|
+
|
|
1752
|
+
if current_nodes.is_empty() {
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
current_nodes
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
fn byte_offset_to_line(source: &str, offset: usize) -> usize {
|
|
1761
|
+
source[..offset.min(source.len())]
|
|
1762
|
+
.chars()
|
|
1763
|
+
.filter(|character| *character == '\n')
|
|
1764
|
+
.count()
|
|
1765
|
+
+ 1
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
fn rebuild_entries<'a>(entries: impl Iterator<Item = &'a str>, indent: &str) -> String {
|
|
1769
|
+
entries
|
|
1770
|
+
.enumerate()
|
|
1771
|
+
.map(|(index, text)| {
|
|
1772
|
+
if index == 0 {
|
|
1773
|
+
text.to_string()
|
|
1774
|
+
} else {
|
|
1775
|
+
format!("\n{}{}", indent, text)
|
|
1776
|
+
}
|
|
1777
|
+
})
|
|
1778
|
+
.collect()
|
|
1779
|
+
}
|