yerba 0.2.2-arm-linux-gnu → 0.4.0-arm-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 +167 -12
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/extconf.rb +39 -12
- data/ext/yerba/include/yerba.h +21 -10
- data/ext/yerba/yerba.c +91 -25
- 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/collection.rb +35 -0
- data/lib/yerba/document.rb +16 -0
- data/lib/yerba/sequence.rb +169 -1
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +7 -2
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +2 -2
- data/rust/cbindgen.toml +1 -0
- data/rust/rustfmt.toml +1 -1
- data/rust/src/commands/blank_lines.rs +1 -4
- data/rust/src/commands/delete.rs +9 -4
- data/rust/src/commands/directives.rs +61 -0
- data/rust/src/commands/get.rs +52 -26
- data/rust/src/commands/insert.rs +8 -4
- data/rust/src/commands/mod.rs +71 -9
- data/rust/src/commands/move_item.rs +2 -1
- data/rust/src/commands/move_key.rs +2 -1
- data/rust/src/commands/quote_style.rs +12 -6
- data/rust/src/commands/remove.rs +8 -4
- data/rust/src/commands/rename.rs +8 -4
- data/rust/src/commands/selectors.rs +158 -0
- data/rust/src/commands/set.rs +33 -16
- data/rust/src/commands/sort.rs +342 -10
- data/rust/src/didyoumean.rs +53 -0
- data/rust/src/document/condition.rs +139 -0
- data/rust/src/document/delete.rs +91 -0
- data/rust/src/document/get.rs +262 -0
- data/rust/src/document/insert.rs +314 -0
- data/rust/src/document/mod.rs +784 -0
- data/rust/src/document/set.rs +90 -0
- data/rust/src/document/sort.rs +607 -0
- data/rust/src/document/style.rs +473 -0
- data/rust/src/error.rs +35 -7
- data/rust/src/ffi.rs +213 -520
- data/rust/src/json.rs +1 -7
- data/rust/src/lib.rs +89 -2
- data/rust/src/main.rs +2 -0
- data/rust/src/quote_style.rs +83 -7
- data/rust/src/selector.rs +2 -7
- data/rust/src/syntax.rs +41 -21
- data/rust/src/yerbafile.rs +39 -18
- metadata +13 -3
- data/rust/src/document.rs +0 -2237
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
5
|
+
let current_node = self.navigate(dot_path)?;
|
|
6
|
+
|
|
7
|
+
if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
|
|
8
|
+
let new_text = if value.is_empty() {
|
|
9
|
+
"\"\"".to_string()
|
|
10
|
+
} else if value.contains('\n') {
|
|
11
|
+
format!("|-\n {}", value.replace('\n', "\n "))
|
|
12
|
+
} else {
|
|
13
|
+
format!("\"{}\"", value.replace('"', "\\\""))
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let range = block_scalar.text_range();
|
|
17
|
+
|
|
18
|
+
return self.apply_edit(range, &new_text);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
22
|
+
|
|
23
|
+
let new_text = format_scalar_value(value, scalar_token.kind());
|
|
24
|
+
|
|
25
|
+
self.replace_token(&scalar_token, &new_text)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
29
|
+
let nodes = self.navigate_all(dot_path);
|
|
30
|
+
|
|
31
|
+
if nodes.is_empty() {
|
|
32
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for node in nodes.into_iter().rev() {
|
|
36
|
+
if let Some(scalar_token) = find_scalar_token(&node) {
|
|
37
|
+
let new_text = format_scalar_value(value, scalar_token.kind());
|
|
38
|
+
|
|
39
|
+
self.replace_token(&scalar_token, &new_text)?;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Ok(())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub fn set_scalar_style(&mut self, dot_path: &str, style: &QuoteStyle) -> Result<(), YerbaError> {
|
|
47
|
+
let current_node = self.navigate(dot_path)?;
|
|
48
|
+
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
49
|
+
|
|
50
|
+
let current_kind = scalar_token.kind();
|
|
51
|
+
let target_kind = style.to_syntax_kind();
|
|
52
|
+
|
|
53
|
+
if current_kind == target_kind {
|
|
54
|
+
return Ok(());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let raw_value = match current_kind {
|
|
58
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
59
|
+
let text = scalar_token.text();
|
|
60
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
64
|
+
let text = scalar_token.text();
|
|
65
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
SyntaxKind::PLAIN_SCALAR => scalar_token.text().to_string(),
|
|
69
|
+
|
|
70
|
+
_ => return Ok(()),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let new_text = format_scalar_value(&raw_value, target_kind);
|
|
74
|
+
|
|
75
|
+
self.replace_token(&scalar_token, &new_text)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub fn set_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
79
|
+
let current_node = self.navigate(dot_path)?;
|
|
80
|
+
|
|
81
|
+
if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
|
|
82
|
+
let range = block_scalar.text_range();
|
|
83
|
+
return self.apply_edit(range, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
87
|
+
|
|
88
|
+
self.replace_token(&scalar_token, value)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn move_item(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
|
|
5
|
+
if from == to {
|
|
6
|
+
return Ok(());
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let current_node = self.navigate(dot_path)?;
|
|
10
|
+
|
|
11
|
+
let sequence = current_node
|
|
12
|
+
.descendants()
|
|
13
|
+
.find_map(BlockSeq::cast)
|
|
14
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
15
|
+
|
|
16
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
17
|
+
|
|
18
|
+
self.reorder_entries(sequence.syntax(), &entries, from, to)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fn move_key(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
|
|
22
|
+
if from == to {
|
|
23
|
+
return Ok(());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let current_node = self.navigate(dot_path)?;
|
|
27
|
+
|
|
28
|
+
let map = current_node
|
|
29
|
+
.descendants()
|
|
30
|
+
.find_map(BlockMap::cast)
|
|
31
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
32
|
+
|
|
33
|
+
let entries: Vec<_> = map.entries().collect();
|
|
34
|
+
|
|
35
|
+
self.reorder_entries(map.syntax(), &entries, from, to)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn resolve_key_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
|
|
39
|
+
let current_node = self.navigate(dot_path)?;
|
|
40
|
+
|
|
41
|
+
let map = current_node
|
|
42
|
+
.descendants()
|
|
43
|
+
.find_map(BlockMap::cast)
|
|
44
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
45
|
+
|
|
46
|
+
if let Ok(index) = reference.parse::<usize>() {
|
|
47
|
+
let length = map.entries().count();
|
|
48
|
+
|
|
49
|
+
if index >= length {
|
|
50
|
+
return Err(YerbaError::IndexOutOfBounds(index, length));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Ok(index);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
map
|
|
57
|
+
.entries()
|
|
58
|
+
.enumerate()
|
|
59
|
+
.find(|(_index, entry)| {
|
|
60
|
+
entry
|
|
61
|
+
.key()
|
|
62
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
63
|
+
.map(|key_text| key_text == reference)
|
|
64
|
+
.unwrap_or(false)
|
|
65
|
+
})
|
|
66
|
+
.map(|(index, _entry)| index)
|
|
67
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} key '{}'", dot_path, reference)))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn resolve_sequence_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
|
|
71
|
+
let current_node = self.navigate(dot_path)?;
|
|
72
|
+
|
|
73
|
+
let sequence = current_node
|
|
74
|
+
.descendants()
|
|
75
|
+
.find_map(BlockSeq::cast)
|
|
76
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
77
|
+
|
|
78
|
+
if let Ok(index) = reference.parse::<usize>() {
|
|
79
|
+
let length = sequence.entries().count();
|
|
80
|
+
|
|
81
|
+
if index >= length {
|
|
82
|
+
return Err(YerbaError::IndexOutOfBounds(index, length));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Ok(index);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if crate::selector::Selector::parse(reference).is_relative() {
|
|
89
|
+
return sequence
|
|
90
|
+
.entries()
|
|
91
|
+
.enumerate()
|
|
92
|
+
.find(|(_index, entry)| self.evaluate_condition_on_node(entry.syntax(), reference))
|
|
93
|
+
.map(|(index, _entry)| index)
|
|
94
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} condition '{}'", dot_path, reference)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
sequence
|
|
98
|
+
.entries()
|
|
99
|
+
.enumerate()
|
|
100
|
+
.find(|(_index, entry)| {
|
|
101
|
+
entry
|
|
102
|
+
.flow()
|
|
103
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
104
|
+
.map(|text| text == reference)
|
|
105
|
+
.unwrap_or(false)
|
|
106
|
+
})
|
|
107
|
+
.map(|(index, _entry)| index)
|
|
108
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} item '{}'", dot_path, reference)))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pub fn validate_sort_keys(&self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
112
|
+
if dot_path == "[]" || dot_path.ends_with(".[]") {
|
|
113
|
+
let sequence_path = if dot_path == "[]" { "" } else { &dot_path[..dot_path.len() - 3] };
|
|
114
|
+
|
|
115
|
+
return self.validate_each_sort_keys(sequence_path, key_order);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let current_node = self.navigate(dot_path)?;
|
|
119
|
+
|
|
120
|
+
let map = current_node
|
|
121
|
+
.descendants()
|
|
122
|
+
.find_map(BlockMap::cast)
|
|
123
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
124
|
+
|
|
125
|
+
let unknown_keys: Vec<String> = map
|
|
126
|
+
.entries()
|
|
127
|
+
.filter_map(|entry| entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())))
|
|
128
|
+
.filter(|key_name| !key_order.contains(&key_name.as_str()))
|
|
129
|
+
.collect();
|
|
130
|
+
|
|
131
|
+
if unknown_keys.is_empty() {
|
|
132
|
+
Ok(())
|
|
133
|
+
} else {
|
|
134
|
+
Err(YerbaError::UnknownKeys(unknown_keys))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub fn sort_keys(&mut self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
139
|
+
if dot_path == "[]" || dot_path.ends_with(".[]") {
|
|
140
|
+
let sequence_path = if dot_path == "[]" { "" } else { &dot_path[..dot_path.len() - 3] };
|
|
141
|
+
|
|
142
|
+
return self.sort_each_keys(sequence_path, key_order);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let current_node = self.navigate(dot_path)?;
|
|
146
|
+
|
|
147
|
+
let map = current_node
|
|
148
|
+
.descendants()
|
|
149
|
+
.find_map(BlockMap::cast)
|
|
150
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
151
|
+
|
|
152
|
+
let entries: Vec<_> = map.entries().collect();
|
|
153
|
+
|
|
154
|
+
if entries.len() <= 1 {
|
|
155
|
+
return Ok(());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let (groups, range) = collect_groups_with_range(map.syntax());
|
|
159
|
+
|
|
160
|
+
let mut keyed: Vec<(String, EntryGroup)> = entries
|
|
161
|
+
.iter()
|
|
162
|
+
.zip(groups)
|
|
163
|
+
.map(|(entry, group)| {
|
|
164
|
+
let key_name = entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())).unwrap_or_default();
|
|
165
|
+
(key_name, group)
|
|
166
|
+
})
|
|
167
|
+
.collect();
|
|
168
|
+
|
|
169
|
+
let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
|
|
170
|
+
|
|
171
|
+
keyed.sort_by(|(key_a, _), (key_b, _)| {
|
|
172
|
+
let position_a = key_order.iter().position(|&key| key == key_a);
|
|
173
|
+
let position_b = key_order.iter().position(|&key| key == key_b);
|
|
174
|
+
|
|
175
|
+
match (position_a, position_b) {
|
|
176
|
+
(Some(a), Some(b)) => a.cmp(&b),
|
|
177
|
+
(Some(_), None) => std::cmp::Ordering::Less,
|
|
178
|
+
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
179
|
+
(None, None) => {
|
|
180
|
+
let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
|
|
181
|
+
let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
|
|
182
|
+
original_a.cmp(&original_b)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
|
|
188
|
+
let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
|
|
189
|
+
|
|
190
|
+
if sorted_keys == orig_refs {
|
|
191
|
+
return Ok(());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let indent = entries.get(1).map(|entry| preceding_whitespace_indent(entry.syntax())).unwrap_or_default();
|
|
195
|
+
|
|
196
|
+
let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
|
|
197
|
+
let map_text = rebuild_from_groups(&sorted_groups, &indent, false);
|
|
198
|
+
|
|
199
|
+
self.apply_edit(range, &map_text)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pub fn sort_each_keys(&mut self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
203
|
+
let current_node = self.navigate(dot_path)?;
|
|
204
|
+
|
|
205
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
206
|
+
Some(sequence) => sequence,
|
|
207
|
+
None => return Ok(()),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
211
|
+
|
|
212
|
+
for entry in sequence.entries() {
|
|
213
|
+
let entry_node = entry.syntax();
|
|
214
|
+
|
|
215
|
+
let map = match entry_node.descendants().find_map(BlockMap::cast) {
|
|
216
|
+
Some(map) => map,
|
|
217
|
+
None => continue,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
let entries: Vec<_> = map.entries().collect();
|
|
221
|
+
|
|
222
|
+
if entries.len() <= 1 {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let (groups, group_range) = collect_groups_with_range(map.syntax());
|
|
227
|
+
|
|
228
|
+
let mut keyed: Vec<(String, EntryGroup)> = entries
|
|
229
|
+
.iter()
|
|
230
|
+
.zip(groups)
|
|
231
|
+
.map(|(entry, group)| {
|
|
232
|
+
let key_name = entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())).unwrap_or_default();
|
|
233
|
+
(key_name, group)
|
|
234
|
+
})
|
|
235
|
+
.collect();
|
|
236
|
+
|
|
237
|
+
let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
|
|
238
|
+
|
|
239
|
+
keyed.sort_by(|(key_a, _), (key_b, _)| {
|
|
240
|
+
let position_a = key_order.iter().position(|&key| key == key_a);
|
|
241
|
+
let position_b = key_order.iter().position(|&key| key == key_b);
|
|
242
|
+
|
|
243
|
+
match (position_a, position_b) {
|
|
244
|
+
(Some(a), Some(b)) => a.cmp(&b),
|
|
245
|
+
(Some(_), None) => std::cmp::Ordering::Less,
|
|
246
|
+
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
247
|
+
|
|
248
|
+
(None, None) => {
|
|
249
|
+
let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
|
|
250
|
+
let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
|
|
251
|
+
|
|
252
|
+
original_a.cmp(&original_b)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
|
|
258
|
+
let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
|
|
259
|
+
|
|
260
|
+
if sorted_keys == orig_refs {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let indent = entries.get(1).map(|entry| preceding_whitespace_indent(entry.syntax())).unwrap_or_default();
|
|
265
|
+
|
|
266
|
+
let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
|
|
267
|
+
let map_text = rebuild_from_groups(&sorted_groups, &indent, false);
|
|
268
|
+
edits.push((group_range, map_text));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if edits.is_empty() {
|
|
272
|
+
return Ok(());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
edits.reverse();
|
|
276
|
+
|
|
277
|
+
let source = self.root.text().to_string();
|
|
278
|
+
let mut new_source = source;
|
|
279
|
+
|
|
280
|
+
for (range, replacement) in edits {
|
|
281
|
+
let start: usize = range.start().into();
|
|
282
|
+
let end: usize = range.end().into();
|
|
283
|
+
|
|
284
|
+
new_source.replace_range(start..end, &replacement);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let path = self.path.take();
|
|
288
|
+
*self = Self::parse(&new_source)?;
|
|
289
|
+
self.path = path;
|
|
290
|
+
|
|
291
|
+
Ok(())
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
pub fn validate_each_sort_keys(&self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
|
|
295
|
+
let current_node = self.navigate(dot_path)?;
|
|
296
|
+
|
|
297
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
298
|
+
Some(sequence) => sequence,
|
|
299
|
+
None => return Ok(()),
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
let mut all_unknown: Vec<String> = Vec::new();
|
|
303
|
+
|
|
304
|
+
for entry in sequence.entries() {
|
|
305
|
+
if let Some(map) = entry.syntax().descendants().find_map(BlockMap::cast) {
|
|
306
|
+
for map_entry in map.entries() {
|
|
307
|
+
if let Some(key_name) = map_entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())) {
|
|
308
|
+
if !key_order.contains(&key_name.as_str()) && !all_unknown.contains(&key_name) {
|
|
309
|
+
all_unknown.push(key_name);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if all_unknown.is_empty() {
|
|
317
|
+
Ok(())
|
|
318
|
+
} else {
|
|
319
|
+
Err(YerbaError::UnknownKeys(all_unknown))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
pub fn sort_items(&mut self, dot_path: &str, sort_fields: &[SortField], case_sensitive: bool) -> Result<(), YerbaError> {
|
|
324
|
+
if dot_path.contains("[].") {
|
|
325
|
+
return self.sort_each_items(dot_path, sort_fields, case_sensitive);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let current_node = self.navigate(dot_path)?;
|
|
329
|
+
|
|
330
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
331
|
+
Some(sequence) => sequence,
|
|
332
|
+
None => return Ok(()),
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
336
|
+
|
|
337
|
+
if entries.len() <= 1 {
|
|
338
|
+
return Ok(());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let (groups, range) = collect_groups_with_range(sequence.syntax());
|
|
342
|
+
|
|
343
|
+
let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
|
|
344
|
+
.iter()
|
|
345
|
+
.zip(groups)
|
|
346
|
+
.map(|(entry, group)| {
|
|
347
|
+
let sort_values = if sort_fields.is_empty() {
|
|
348
|
+
vec![entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())).unwrap_or_default()]
|
|
349
|
+
} else {
|
|
350
|
+
sort_fields
|
|
351
|
+
.iter()
|
|
352
|
+
.map(|field| {
|
|
353
|
+
if field.path.is_empty() {
|
|
354
|
+
entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())).unwrap_or_default()
|
|
355
|
+
} else {
|
|
356
|
+
let nodes = navigate_from_node(entry.syntax(), &field.path);
|
|
357
|
+
nodes.first().and_then(extract_scalar_text).unwrap_or_default()
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
.collect()
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
(sort_values, group)
|
|
364
|
+
})
|
|
365
|
+
.collect();
|
|
366
|
+
|
|
367
|
+
let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
|
|
368
|
+
|
|
369
|
+
sortable.sort_by(|(values_a, _), (values_b, _)| {
|
|
370
|
+
for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
|
|
371
|
+
let value_a = &values_a[index];
|
|
372
|
+
let value_b = &values_b[index];
|
|
373
|
+
|
|
374
|
+
let ordering = if case_sensitive {
|
|
375
|
+
value_a.cmp(value_b)
|
|
376
|
+
} else {
|
|
377
|
+
value_a.to_lowercase().cmp(&value_b.to_lowercase())
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
let ordering = if field.ascending { ordering } else { ordering.reverse() };
|
|
381
|
+
|
|
382
|
+
if ordering != std::cmp::Ordering::Equal {
|
|
383
|
+
return ordering;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
|
|
388
|
+
return if case_sensitive {
|
|
389
|
+
values_a[0].cmp(&values_b[0])
|
|
390
|
+
} else {
|
|
391
|
+
values_a[0].to_lowercase().cmp(&values_b[0].to_lowercase())
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
std::cmp::Ordering::Equal
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
|
|
399
|
+
|
|
400
|
+
if sorted_bodies == original_bodies {
|
|
401
|
+
return Ok(());
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let indent = entries.get(1).map(|entry| preceding_whitespace_indent(entry.syntax())).unwrap_or_default();
|
|
405
|
+
|
|
406
|
+
let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
|
|
407
|
+
let sequence_text = rebuild_from_groups(&sorted_groups, &indent, true);
|
|
408
|
+
|
|
409
|
+
self.apply_edit(range, &sequence_text)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
fn sort_each_items(&mut self, dot_path: &str, sort_fields: &[SortField], case_sensitive: bool) -> Result<(), YerbaError> {
|
|
413
|
+
let (parent_path, child_path) = if let Some(last_bracket) = dot_path.rfind("[].") {
|
|
414
|
+
(&dot_path[..last_bracket + 2], &dot_path[last_bracket + 3..])
|
|
415
|
+
} else {
|
|
416
|
+
(dot_path, "")
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
let parent_nodes = self.navigate_all(parent_path);
|
|
420
|
+
let source = self.root.text().to_string();
|
|
421
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
422
|
+
|
|
423
|
+
for parent_node in &parent_nodes {
|
|
424
|
+
let child_nodes = if child_path.is_empty() {
|
|
425
|
+
vec![parent_node.clone()]
|
|
426
|
+
} else {
|
|
427
|
+
navigate_from_node(parent_node, child_path)
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
for child_node in &child_nodes {
|
|
431
|
+
let sequence = match child_node.descendants().find_map(BlockSeq::cast) {
|
|
432
|
+
Some(sequence) => sequence,
|
|
433
|
+
None => continue,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
437
|
+
|
|
438
|
+
if entries.len() <= 1 {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let (groups, group_range) = collect_groups_with_range(sequence.syntax());
|
|
443
|
+
|
|
444
|
+
let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
|
|
445
|
+
.iter()
|
|
446
|
+
.zip(groups)
|
|
447
|
+
.map(|(entry, group)| {
|
|
448
|
+
let sort_values = if sort_fields.is_empty() {
|
|
449
|
+
vec![entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())).unwrap_or_default()]
|
|
450
|
+
} else {
|
|
451
|
+
sort_fields
|
|
452
|
+
.iter()
|
|
453
|
+
.map(|field| {
|
|
454
|
+
if field.path.is_empty() {
|
|
455
|
+
entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())).unwrap_or_default()
|
|
456
|
+
} else {
|
|
457
|
+
let nodes = navigate_from_node(entry.syntax(), &field.path);
|
|
458
|
+
nodes.first().and_then(extract_scalar_text).unwrap_or_default()
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
.collect()
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
(sort_values, group)
|
|
465
|
+
})
|
|
466
|
+
.collect();
|
|
467
|
+
|
|
468
|
+
let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
|
|
469
|
+
|
|
470
|
+
sortable.sort_by(|(values_a, _), (values_b, _)| {
|
|
471
|
+
for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
|
|
472
|
+
let value_a = &values_a[index];
|
|
473
|
+
let value_b = &values_b[index];
|
|
474
|
+
|
|
475
|
+
let ordering = if case_sensitive {
|
|
476
|
+
value_a.cmp(value_b)
|
|
477
|
+
} else {
|
|
478
|
+
value_a.to_lowercase().cmp(&value_b.to_lowercase())
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
let ordering = if field.ascending { ordering } else { ordering.reverse() };
|
|
482
|
+
|
|
483
|
+
if ordering != std::cmp::Ordering::Equal {
|
|
484
|
+
return ordering;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
|
|
489
|
+
return if case_sensitive {
|
|
490
|
+
values_a[0].cmp(&values_b[0])
|
|
491
|
+
} else {
|
|
492
|
+
values_a[0].to_lowercase().cmp(&values_b[0].to_lowercase())
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
std::cmp::Ordering::Equal
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
|
|
500
|
+
|
|
501
|
+
if sorted_bodies == original_bodies {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let indent = entries.get(1).map(|entry| preceding_whitespace_indent(entry.syntax())).unwrap_or_default();
|
|
506
|
+
let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
|
|
507
|
+
let sequence_text = rebuild_from_groups(&sorted_groups, &indent, true);
|
|
508
|
+
|
|
509
|
+
edits.push((group_range, sequence_text));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if edits.is_empty() {
|
|
514
|
+
return Ok(());
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
edits.reverse();
|
|
518
|
+
|
|
519
|
+
let mut new_source = source;
|
|
520
|
+
|
|
521
|
+
for (range, replacement) in edits {
|
|
522
|
+
let start: usize = range.start().into();
|
|
523
|
+
let end: usize = range.end().into();
|
|
524
|
+
|
|
525
|
+
new_source.replace_range(start..end, &replacement);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let path = self.path.take();
|
|
529
|
+
*self = Self::parse(&new_source)?;
|
|
530
|
+
self.path = path;
|
|
531
|
+
|
|
532
|
+
Ok(())
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
pub fn reorder_items(&mut self, dot_path: &str, by: &str, desired_order: &[&str]) -> Result<(), YerbaError> {
|
|
536
|
+
let field = by.strip_prefix('.').unwrap_or(by);
|
|
537
|
+
let is_scalar = field.is_empty() || field == ".";
|
|
538
|
+
|
|
539
|
+
let items_selector = if is_scalar {
|
|
540
|
+
if dot_path.is_empty() {
|
|
541
|
+
"[]".to_string()
|
|
542
|
+
} else {
|
|
543
|
+
format!("{}[]", dot_path)
|
|
544
|
+
}
|
|
545
|
+
} else if dot_path.is_empty() {
|
|
546
|
+
format!("[].{}", field)
|
|
547
|
+
} else {
|
|
548
|
+
format!("{}[].{}", dot_path, field)
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
let labels: Vec<String> = match self.get_value(&items_selector) {
|
|
552
|
+
Some(serde_yaml::Value::Sequence(sequence)) => sequence
|
|
553
|
+
.iter()
|
|
554
|
+
.map(|value| match value {
|
|
555
|
+
serde_yaml::Value::String(string) => string.clone(),
|
|
556
|
+
_ => String::new(),
|
|
557
|
+
})
|
|
558
|
+
.collect(),
|
|
559
|
+
_ => return Err(YerbaError::SelectorNotFound(items_selector)),
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
let mut used = vec![false; labels.len()];
|
|
563
|
+
let mut moves: Vec<usize> = Vec::new();
|
|
564
|
+
|
|
565
|
+
for desired in desired_order {
|
|
566
|
+
let found = labels.iter().enumerate().find(|(index, label)| label.as_str() == *desired && !used[*index]);
|
|
567
|
+
|
|
568
|
+
if let Some((index, _)) = found {
|
|
569
|
+
moves.push(index);
|
|
570
|
+
used[index] = true;
|
|
571
|
+
} else {
|
|
572
|
+
return Err(YerbaError::SelectorNotFound(format!("no item found with {} == \"{}\"", by, desired)));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let missing: Vec<&String> = labels.iter().enumerate().filter(|(index, _)| !used[*index]).map(|(_, label)| label).collect();
|
|
577
|
+
|
|
578
|
+
if !missing.is_empty() {
|
|
579
|
+
let missing_list = missing.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", ");
|
|
580
|
+
|
|
581
|
+
return Err(YerbaError::SelectorNotFound(format!(
|
|
582
|
+
"order must specify all {} items, but {} missing: {}",
|
|
583
|
+
labels.len(),
|
|
584
|
+
missing.len(),
|
|
585
|
+
missing_list
|
|
586
|
+
)));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
for target in 0..moves.len() {
|
|
590
|
+
let source = moves[target];
|
|
591
|
+
|
|
592
|
+
if source != target {
|
|
593
|
+
self.move_item(dot_path, source, target)?;
|
|
594
|
+
|
|
595
|
+
for item in moves.iter_mut().skip(target + 1) {
|
|
596
|
+
if *item >= target && *item < source {
|
|
597
|
+
*item += 1;
|
|
598
|
+
} else if *item == source {
|
|
599
|
+
*item = target;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
Ok(())
|
|
606
|
+
}
|
|
607
|
+
}
|