yerba 0.3.0-arm-linux-gnu → 0.4.1-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 +93 -8
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/extconf.rb +22 -3
- data/ext/yerba/include/yerba.h +32 -9
- data/ext/yerba/yerba.c +133 -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 +45 -3
- data/lib/yerba/query_result.rb +50 -0
- data/lib/yerba/sequence.rb +187 -1
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba/yerbafile.rb +45 -0
- data/lib/yerba.rb +2 -0
- data/rust/Cargo.lock +3 -1
- data/rust/Cargo.toml +3 -2
- data/rust/cbindgen.toml +1 -0
- data/rust/rustfmt.toml +1 -1
- data/rust/src/commands/apply.rs +11 -2
- data/rust/src/commands/blank_lines.rs +1 -4
- data/rust/src/commands/check.rs +11 -2
- data/rust/src/commands/directives.rs +61 -0
- data/rust/src/commands/get.rs +5 -22
- data/rust/src/commands/mod.rs +16 -18
- data/rust/src/commands/quote_style.rs +12 -6
- data/rust/src/commands/selectors.rs +3 -19
- data/rust/src/commands/sort.rs +22 -157
- data/rust/src/didyoumean.rs +2 -4
- 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 +384 -0
- data/rust/src/document/mod.rs +784 -0
- data/rust/src/document/set.rs +100 -0
- data/rust/src/document/sort.rs +639 -0
- data/rust/src/document/style.rs +473 -0
- data/rust/src/error.rs +24 -6
- data/rust/src/ffi.rs +272 -518
- data/rust/src/json.rs +1 -7
- data/rust/src/lib.rs +88 -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 +86 -19
- metadata +13 -3
- data/rust/src/document.rs +0 -2304
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
|
|
5
|
+
let nodes = if dot_path.contains('[') {
|
|
6
|
+
self.navigate_all(dot_path)
|
|
7
|
+
} else {
|
|
8
|
+
vec![self.navigate(dot_path)?]
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
12
|
+
|
|
13
|
+
for current_node in &nodes {
|
|
14
|
+
let sequence = current_node.descendants().find_map(BlockSeq::cast);
|
|
15
|
+
let map = current_node.descendants().find_map(BlockMap::cast);
|
|
16
|
+
|
|
17
|
+
let use_sequence = match (&sequence, &map) {
|
|
18
|
+
(Some(sequence), Some(map)) => sequence.syntax().text_range().start() <= map.syntax().text_range().start(),
|
|
19
|
+
(Some(_), None) => true,
|
|
20
|
+
(None, Some(_)) => false,
|
|
21
|
+
(None, None) => continue,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if use_sequence {
|
|
25
|
+
let entries: Vec<_> = sequence.unwrap().entries().collect();
|
|
26
|
+
|
|
27
|
+
if entries.len() > 1 {
|
|
28
|
+
for entry in entries.iter().skip(1) {
|
|
29
|
+
collect_blank_line_edits(entry.syntax(), blank_lines, &mut edits);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
let entries: Vec<_> = map.unwrap().entries().collect();
|
|
34
|
+
|
|
35
|
+
if entries.len() > 1 {
|
|
36
|
+
for entry in entries.iter().skip(1) {
|
|
37
|
+
collect_blank_line_edits(entry.syntax(), blank_lines, &mut edits);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if edits.is_empty() {
|
|
44
|
+
return Ok(());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
edits.sort_by_key(|edit| std::cmp::Reverse(edit.0.start()));
|
|
48
|
+
|
|
49
|
+
let source = self.root.text().to_string();
|
|
50
|
+
let mut new_source = source;
|
|
51
|
+
|
|
52
|
+
for (range, replacement) in edits {
|
|
53
|
+
let start: usize = range.start().into();
|
|
54
|
+
let end: usize = range.end().into();
|
|
55
|
+
|
|
56
|
+
new_source.replace_range(start..end, &replacement);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let path = self.path.take();
|
|
60
|
+
*self = Self::parse(&new_source)?;
|
|
61
|
+
self.path = path;
|
|
62
|
+
|
|
63
|
+
Ok(())
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn has_directives_marker(&self) -> bool {
|
|
67
|
+
self.root.descendants_with_tokens().any(|element| element.kind() == SyntaxKind::DIRECTIVES_END)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn ensure_directives(&mut self) -> Result<(), YerbaError> {
|
|
71
|
+
if self.has_directives_marker() {
|
|
72
|
+
return Ok(());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let source = self.root.text().to_string();
|
|
76
|
+
let new_source = format!("---\n{}", source);
|
|
77
|
+
|
|
78
|
+
let path = self.path.take();
|
|
79
|
+
*self = Self::parse(&new_source)?;
|
|
80
|
+
self.path = path;
|
|
81
|
+
|
|
82
|
+
Ok(())
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn remove_directives(&mut self) -> Result<(), YerbaError> {
|
|
86
|
+
if !self.has_directives_marker() {
|
|
87
|
+
return Ok(());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let source = self.root.text().to_string();
|
|
91
|
+
let new_source = source
|
|
92
|
+
.strip_prefix("---\n")
|
|
93
|
+
.or_else(|| source.strip_prefix("---"))
|
|
94
|
+
.unwrap_or(&source)
|
|
95
|
+
.to_string();
|
|
96
|
+
|
|
97
|
+
let path = self.path.take();
|
|
98
|
+
*self = Self::parse(&new_source)?;
|
|
99
|
+
self.path = path;
|
|
100
|
+
|
|
101
|
+
Ok(())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn enforce_key_style(&mut self, style: &crate::KeyStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
|
|
105
|
+
let source = self.root.text().to_string();
|
|
106
|
+
|
|
107
|
+
let scope_ranges: Vec<TextRange> = match dot_path {
|
|
108
|
+
Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
|
|
109
|
+
_ => vec![self.root.text_range()],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
113
|
+
|
|
114
|
+
for element in self.root.descendants_with_tokens() {
|
|
115
|
+
if let Some(token) = element.into_token() {
|
|
116
|
+
if !scope_ranges.iter().any(|range| range.contains_range(token.text_range())) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if !is_map_key(&token) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let current_kind = token.kind();
|
|
125
|
+
|
|
126
|
+
if !matches!(
|
|
127
|
+
current_kind,
|
|
128
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
129
|
+
) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let target_kind = style.to_syntax_kind();
|
|
134
|
+
|
|
135
|
+
if current_kind == target_kind {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let raw_value = match current_kind {
|
|
140
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
141
|
+
let text = token.text();
|
|
142
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
146
|
+
let text = token.text();
|
|
147
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
151
|
+
|
|
152
|
+
_ => continue,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
let new_text = match style {
|
|
156
|
+
crate::KeyStyle::Double => {
|
|
157
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
158
|
+
format!("\"{}\"", escaped)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
crate::KeyStyle::Single => {
|
|
162
|
+
let escaped = raw_value.replace('\'', "''");
|
|
163
|
+
format!("'{}'", escaped)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
crate::KeyStyle::Plain => raw_value,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if new_text != token.text() {
|
|
170
|
+
edits.push((token.text_range(), new_text));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if edits.is_empty() {
|
|
176
|
+
return Ok(());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
edits.reverse();
|
|
180
|
+
|
|
181
|
+
let mut new_source = source;
|
|
182
|
+
|
|
183
|
+
for (range, replacement) in edits {
|
|
184
|
+
let start: usize = range.start().into();
|
|
185
|
+
let end: usize = range.end().into();
|
|
186
|
+
|
|
187
|
+
new_source.replace_range(start..end, &replacement);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let path = self.path.take();
|
|
191
|
+
*self = Self::parse(&new_source)?;
|
|
192
|
+
self.path = path;
|
|
193
|
+
|
|
194
|
+
Ok(())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pub fn enforce_quote_style(
|
|
198
|
+
&mut self,
|
|
199
|
+
key_style: Option<&crate::KeyStyle>,
|
|
200
|
+
value_style: Option<&QuoteStyle>,
|
|
201
|
+
selector: Option<&str>,
|
|
202
|
+
) -> Result<(), YerbaError> {
|
|
203
|
+
if let Some(key_style) = key_style {
|
|
204
|
+
self.enforce_key_style(key_style, selector)?;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if let Some(value_style) = value_style {
|
|
208
|
+
self.enforce_quotes_at(value_style, selector)?;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Ok(())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub fn enforce_quotes(&mut self, style: &QuoteStyle) -> Result<Vec<String>, YerbaError> {
|
|
215
|
+
self.enforce_quotes_at(style, None)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub fn enforce_quotes_at(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<Vec<String>, YerbaError> {
|
|
219
|
+
let source = self.root.text().to_string();
|
|
220
|
+
|
|
221
|
+
let scope_ranges: Vec<TextRange> = match dot_path {
|
|
222
|
+
Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
|
|
223
|
+
_ => vec![self.root.text_range()],
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
227
|
+
let mut warnings: Vec<String> = Vec::new();
|
|
228
|
+
|
|
229
|
+
for element in self.root.descendants_with_tokens() {
|
|
230
|
+
if let Some(token) = element.into_token() {
|
|
231
|
+
if !scope_ranges.iter().any(|range| range.contains_range(token.text_range())) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if is_map_key(&token) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let current_kind = token.kind();
|
|
240
|
+
let is_block_scalar_target = style.is_block_scalar();
|
|
241
|
+
|
|
242
|
+
let is_inline_scalar = matches!(
|
|
243
|
+
current_kind,
|
|
244
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
let is_block_scalar_text = current_kind == SyntaxKind::BLOCK_SCALAR_TEXT;
|
|
248
|
+
|
|
249
|
+
if !is_inline_scalar && !is_block_scalar_text {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if is_block_scalar_text && is_block_scalar_target {
|
|
254
|
+
if let Some(parent) = token.parent() {
|
|
255
|
+
if parent.kind() == SyntaxKind::BLOCK_SCALAR {
|
|
256
|
+
let parent_text = parent.text().to_string();
|
|
257
|
+
let target_header = style.block_header();
|
|
258
|
+
let current_header = parent_text.lines().next().unwrap_or("");
|
|
259
|
+
|
|
260
|
+
if current_header != target_header {
|
|
261
|
+
let content = parent_text.strip_prefix(current_header).unwrap_or(&parent_text);
|
|
262
|
+
let new_text = format!("{}{}", target_header, content);
|
|
263
|
+
|
|
264
|
+
edits.push((parent.text_range(), new_text));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if is_block_scalar_text && !is_block_scalar_target {
|
|
273
|
+
if dot_path.is_none() || dot_path == Some("") {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if let Some(parent) = token.parent() {
|
|
278
|
+
if parent.kind() == SyntaxKind::BLOCK_SCALAR {
|
|
279
|
+
let raw_text = token.text().to_string();
|
|
280
|
+
let lines: Vec<&str> = raw_text.lines().collect();
|
|
281
|
+
|
|
282
|
+
let min_indent = lines
|
|
283
|
+
.iter()
|
|
284
|
+
.filter(|line| !line.trim().is_empty())
|
|
285
|
+
.map(|line| line.len() - line.trim_start().len())
|
|
286
|
+
.min()
|
|
287
|
+
.unwrap_or(0);
|
|
288
|
+
|
|
289
|
+
let dedented: String = lines
|
|
290
|
+
.iter()
|
|
291
|
+
.map(|line| if line.len() >= min_indent { &line[min_indent..] } else { line.trim() })
|
|
292
|
+
.collect::<Vec<_>>()
|
|
293
|
+
.join("\n");
|
|
294
|
+
|
|
295
|
+
let trimmed = dedented.trim();
|
|
296
|
+
let is_multiline = trimmed.contains('\n');
|
|
297
|
+
|
|
298
|
+
let new_text = match style {
|
|
299
|
+
QuoteStyle::Double => {
|
|
300
|
+
let escaped = trimmed.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n");
|
|
301
|
+
|
|
302
|
+
format!("\"{}\"", escaped)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
QuoteStyle::Single => {
|
|
306
|
+
if is_multiline {
|
|
307
|
+
let offset: usize = token.text_range().start().into();
|
|
308
|
+
let line = source[..offset].matches('\n').count() + 1;
|
|
309
|
+
|
|
310
|
+
warnings.push(format!(
|
|
311
|
+
"line {}: skipped block scalar → single (multiline content can't be single-quoted)",
|
|
312
|
+
line
|
|
313
|
+
));
|
|
314
|
+
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let escaped = trimmed.replace('\'', "''");
|
|
319
|
+
|
|
320
|
+
format!("'{}'", escaped)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
QuoteStyle::Plain => {
|
|
324
|
+
if is_multiline || trimmed.contains(':') || trimmed.contains('#') || trimmed.contains('"') || trimmed.contains('\'') {
|
|
325
|
+
let offset: usize = token.text_range().start().into();
|
|
326
|
+
let line = source[..offset].matches('\n').count() + 1;
|
|
327
|
+
let reason = if is_multiline { "multiline content" } else { "special characters" };
|
|
328
|
+
|
|
329
|
+
warnings.push(format!("line {}: skipped block scalar → plain ({} can't be plain)", line, reason));
|
|
330
|
+
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
trimmed.to_string()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
_ => continue,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
edits.push((parent.text_range(), new_text));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if is_inline_scalar && is_block_scalar_target {
|
|
348
|
+
let raw_value = match current_kind {
|
|
349
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
350
|
+
let text = token.text();
|
|
351
|
+
|
|
352
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
356
|
+
let text = token.text();
|
|
357
|
+
|
|
358
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
362
|
+
|
|
363
|
+
_ => continue,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
if raw_value.is_empty() || is_yaml_non_string(&raw_value) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let offset: usize = token.text_range().start().into();
|
|
371
|
+
let line_start = source[..offset].rfind('\n').map(|p| p + 1).unwrap_or(0);
|
|
372
|
+
let line_prefix = &source[line_start..offset];
|
|
373
|
+
let indent = line_prefix.len() - line_prefix.trim_start().len() + 2;
|
|
374
|
+
let indent_str = " ".repeat(indent);
|
|
375
|
+
let header = style.block_header();
|
|
376
|
+
|
|
377
|
+
let indented = raw_value
|
|
378
|
+
.lines()
|
|
379
|
+
.map(|line| if line.is_empty() { String::new() } else { format!("{}{}", indent_str, line) })
|
|
380
|
+
.collect::<Vec<_>>()
|
|
381
|
+
.join("\n");
|
|
382
|
+
|
|
383
|
+
let new_text = format!("{}\n{}", header, indented);
|
|
384
|
+
|
|
385
|
+
edits.push((token.text_range(), new_text));
|
|
386
|
+
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if !is_inline_scalar {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let target_kind = style.to_syntax_kind();
|
|
395
|
+
|
|
396
|
+
if current_kind == target_kind {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let raw_value = match current_kind {
|
|
401
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
402
|
+
let text = token.text();
|
|
403
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
407
|
+
let text = token.text();
|
|
408
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
412
|
+
|
|
413
|
+
_ => continue,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
if is_yaml_non_string(&raw_value) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let new_text = match style {
|
|
421
|
+
QuoteStyle::Double => {
|
|
422
|
+
if raw_value.contains('"') && current_kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
427
|
+
format!("\"{}\"", escaped)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
QuoteStyle::Single => {
|
|
431
|
+
let escaped = raw_value.replace('\'', "''");
|
|
432
|
+
format!("'{}'", escaped)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
QuoteStyle::Plain => {
|
|
436
|
+
if raw_value.contains('"') || raw_value.contains('\'') || raw_value.contains(':') || raw_value.contains('#') {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
raw_value
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_ => continue,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
if new_text != token.text() {
|
|
447
|
+
edits.push((token.text_range(), new_text));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if edits.is_empty() {
|
|
453
|
+
return Ok(warnings);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
edits.reverse();
|
|
457
|
+
|
|
458
|
+
let mut new_source = source;
|
|
459
|
+
|
|
460
|
+
for (range, replacement) in edits {
|
|
461
|
+
let start: usize = range.start().into();
|
|
462
|
+
let end: usize = range.end().into();
|
|
463
|
+
|
|
464
|
+
new_source.replace_range(start..end, &replacement);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let path = self.path.take();
|
|
468
|
+
*self = Self::parse(&new_source)?;
|
|
469
|
+
self.path = path;
|
|
470
|
+
|
|
471
|
+
Ok(warnings)
|
|
472
|
+
}
|
|
473
|
+
}
|
data/rust/src/error.rs
CHANGED
|
@@ -7,6 +7,12 @@ pub enum YerbaError {
|
|
|
7
7
|
NotASequence(String),
|
|
8
8
|
IndexOutOfBounds(usize, usize),
|
|
9
9
|
UnknownKeys(Vec<String>),
|
|
10
|
+
DuplicateKey {
|
|
11
|
+
key: String,
|
|
12
|
+
first_line: usize,
|
|
13
|
+
duplicate_line: usize,
|
|
14
|
+
line_content: String,
|
|
15
|
+
},
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
impl std::fmt::Display for YerbaError {
|
|
@@ -15,6 +21,7 @@ impl std::fmt::Display for YerbaError {
|
|
|
15
21
|
YerbaError::ParseError(msg) => write!(f, "parse error: {}", msg),
|
|
16
22
|
YerbaError::IoError(err) => write!(f, "io error: {}", err),
|
|
17
23
|
YerbaError::SelectorNotFound(selector) => write!(f, "selector not found: {}", selector),
|
|
24
|
+
YerbaError::NotASequence(path) => write!(f, "not a sequence: {}", path),
|
|
18
25
|
|
|
19
26
|
YerbaError::AmbiguousSelector(selector, count) => {
|
|
20
27
|
write!(
|
|
@@ -24,18 +31,29 @@ impl std::fmt::Display for YerbaError {
|
|
|
24
31
|
)
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
YerbaError::
|
|
34
|
+
YerbaError::DuplicateKey {
|
|
35
|
+
key,
|
|
36
|
+
first_line,
|
|
37
|
+
duplicate_line,
|
|
38
|
+
line_content,
|
|
39
|
+
} => {
|
|
40
|
+
write!(
|
|
41
|
+
f,
|
|
42
|
+
"duplicate key \"{}\" on line {} (first defined on line {})\n\n {} | {}",
|
|
43
|
+
key,
|
|
44
|
+
duplicate_line,
|
|
45
|
+
first_line,
|
|
46
|
+
duplicate_line,
|
|
47
|
+
line_content.trim()
|
|
48
|
+
)
|
|
49
|
+
}
|
|
28
50
|
|
|
29
51
|
YerbaError::IndexOutOfBounds(index, length) => {
|
|
30
52
|
write!(f, "index {} out of bounds (length {})", index, length)
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
YerbaError::UnknownKeys(keys) => {
|
|
34
|
-
let suggestion = keys
|
|
35
|
-
.iter()
|
|
36
|
-
.map(|key| format!("\"{}\"", key))
|
|
37
|
-
.collect::<Vec<_>>()
|
|
38
|
-
.join(", ");
|
|
56
|
+
let suggestion = keys.iter().map(|key| format!("\"{}\"", key)).collect::<Vec<_>>().join(", ");
|
|
39
57
|
|
|
40
58
|
write!(
|
|
41
59
|
f,
|