yerba 0.5.1-x86_64-linux-gnu → 0.6.0-x86_64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -25
- data/exe/x86_64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +12 -0
- data/ext/yerba/yerba.c +74 -0
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/formatting.rb +61 -0
- data/lib/yerba/map.rb +50 -6
- data/lib/yerba/sequence.rb +20 -0
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/document/delete.rs +14 -1
- data/rust/src/document/get.rs +47 -22
- data/rust/src/document/insert.rs +201 -6
- data/rust/src/document/mod.rs +54 -4
- data/rust/src/document/style.rs +609 -2
- data/rust/src/ffi.rs +46 -0
- data/rust/src/lib.rs +1 -0
- data/rust/src/yaml_writer.rs +98 -0
- data/rust/src/yerbafile.rs +139 -4
- metadata +2 -2
data/rust/src/document/style.rs
CHANGED
|
@@ -1,6 +1,613 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
+
pub struct StyleEnforcement {
|
|
4
|
+
pub collection_style: Option<String>,
|
|
5
|
+
pub sequence_indent: Option<String>,
|
|
6
|
+
pub key_style: Option<crate::KeyStyle>,
|
|
7
|
+
pub value_style: Option<QuoteStyle>,
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
impl Document {
|
|
11
|
+
pub fn enforce_styles(&mut self, enforcement: &StyleEnforcement) -> Result<(), YerbaError> {
|
|
12
|
+
let source = self.root.text().to_string();
|
|
13
|
+
let mut edits: Vec<(TextRange, String)> = Vec::new();
|
|
14
|
+
let mut converted_ranges: Vec<TextRange> = Vec::new();
|
|
15
|
+
|
|
16
|
+
let want_block_collections = enforcement.collection_style.as_deref() == Some("block");
|
|
17
|
+
let want_indented = enforcement.sequence_indent.as_deref() == Some("indented");
|
|
18
|
+
let want_compact = enforcement.sequence_indent.as_deref() == Some("compact");
|
|
19
|
+
|
|
20
|
+
for element in self.root.descendants_with_tokens() {
|
|
21
|
+
match element {
|
|
22
|
+
rowan::NodeOrToken::Node(ref node) => {
|
|
23
|
+
if want_block_collections && (node.kind() == SyntaxKind::FLOW_SEQ || node.kind() == SyntaxKind::FLOW_MAP) {
|
|
24
|
+
if node
|
|
25
|
+
.ancestors()
|
|
26
|
+
.skip(1)
|
|
27
|
+
.any(|ancestor| ancestor.kind() == SyntaxKind::FLOW_SEQ || ancestor.kind() == SyntaxKind::FLOW_MAP)
|
|
28
|
+
{
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let value = node_to_yaml_value(node);
|
|
33
|
+
|
|
34
|
+
match &value {
|
|
35
|
+
yaml_serde::Value::Sequence(sequence) if sequence.is_empty() => continue,
|
|
36
|
+
yaml_serde::Value::Mapping(mapping) if mapping.is_empty() => continue,
|
|
37
|
+
_ => {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let entry_node = node.ancestors().find(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_ENTRY);
|
|
41
|
+
|
|
42
|
+
if let Some(ref entry) = entry_node {
|
|
43
|
+
let entry_start: usize = entry.text_range().start().into();
|
|
44
|
+
let entry_line_start = source[..entry_start].rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
45
|
+
let key_indent = entry_start - entry_line_start;
|
|
46
|
+
let entry_text = entry.text().to_string();
|
|
47
|
+
|
|
48
|
+
if let Some(colon_offset) = entry_text.find(':') {
|
|
49
|
+
let colon_position = entry_start + colon_offset;
|
|
50
|
+
let value_indent = key_indent + 2;
|
|
51
|
+
let block_text = crate::yaml_writer::yaml_value_to_block_text(&value, value_indent);
|
|
52
|
+
let replace_range = TextRange::new(rowan::TextSize::from((colon_position + 1) as u32), node.text_range().end());
|
|
53
|
+
|
|
54
|
+
converted_ranges.push(node.text_range());
|
|
55
|
+
edits.push((replace_range, format!("\n{}", block_text)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (want_indented || want_compact) && BlockSeq::can_cast(node.kind()) {
|
|
61
|
+
if node.ancestors().skip(1).any(|ancestor| BlockSeq::can_cast(ancestor.kind())) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let parent_entry = match node.ancestors().find(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_ENTRY) {
|
|
66
|
+
Some(entry) => entry,
|
|
67
|
+
None => continue,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let sequence = match BlockSeq::cast(node.clone()) {
|
|
71
|
+
Some(sequence) => sequence,
|
|
72
|
+
None => continue,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let first_entry = match sequence.entries().next() {
|
|
76
|
+
Some(entry) => entry,
|
|
77
|
+
None => continue,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let entry_start: usize = parent_entry.text_range().start().into();
|
|
81
|
+
let line_start = source[..entry_start].rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
82
|
+
let key_indent_length = entry_start - line_start;
|
|
83
|
+
let entry_indent_length = preceding_whitespace_indent(first_entry.syntax()).len();
|
|
84
|
+
let is_indented = entry_indent_length > key_indent_length;
|
|
85
|
+
let needs_change = (want_indented && !is_indented) || (want_compact && is_indented);
|
|
86
|
+
|
|
87
|
+
if needs_change {
|
|
88
|
+
let target_style = enforcement.sequence_indent.as_deref().unwrap();
|
|
89
|
+
|
|
90
|
+
let indent_diff: i32 = match target_style {
|
|
91
|
+
"indented" => (key_indent_length as i32 + 2) - entry_indent_length as i32,
|
|
92
|
+
"compact" => key_indent_length as i32 - entry_indent_length as i32,
|
|
93
|
+
_ => continue,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if indent_diff == 0 {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let sequence_range = node.text_range();
|
|
101
|
+
let sequence_start: usize = sequence_range.start().into();
|
|
102
|
+
let sequence_end: usize = sequence_range.end().into();
|
|
103
|
+
|
|
104
|
+
let before_sequence = &source[..sequence_start];
|
|
105
|
+
let line_start_of_first_entry = before_sequence.rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
106
|
+
let full_text = &source[line_start_of_first_entry..sequence_end];
|
|
107
|
+
|
|
108
|
+
let reindented: String = full_text
|
|
109
|
+
.lines()
|
|
110
|
+
.map(|line| {
|
|
111
|
+
if line.trim().is_empty() {
|
|
112
|
+
String::new()
|
|
113
|
+
} else {
|
|
114
|
+
let current_line_indent = line.len() - line.trim_start().len();
|
|
115
|
+
let new_indent = (current_line_indent as i32 + indent_diff).max(0) as usize;
|
|
116
|
+
format!("{}{}", " ".repeat(new_indent), line.trim_start())
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.collect::<Vec<_>>()
|
|
120
|
+
.join("\n");
|
|
121
|
+
|
|
122
|
+
let replace_range = TextRange::new(rowan::TextSize::from(line_start_of_first_entry as u32), sequence_range.end());
|
|
123
|
+
|
|
124
|
+
edits.push((replace_range, reindented));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
rowan::NodeOrToken::Token(ref token) => {
|
|
130
|
+
if converted_ranges.iter().any(|range| range.contains_range(token.text_range())) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let current_kind = token.kind();
|
|
135
|
+
|
|
136
|
+
if let Some(ref key_style) = enforcement.key_style {
|
|
137
|
+
if is_map_key(token)
|
|
138
|
+
&& matches!(
|
|
139
|
+
current_kind,
|
|
140
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
141
|
+
)
|
|
142
|
+
{
|
|
143
|
+
let target_kind = key_style.to_syntax_kind();
|
|
144
|
+
|
|
145
|
+
if current_kind != target_kind {
|
|
146
|
+
let raw_value = match current_kind {
|
|
147
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
148
|
+
let text = token.text();
|
|
149
|
+
|
|
150
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
154
|
+
let text = token.text();
|
|
155
|
+
|
|
156
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
160
|
+
|
|
161
|
+
_ => continue,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
let new_text = match key_style {
|
|
165
|
+
crate::KeyStyle::Double => {
|
|
166
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
167
|
+
|
|
168
|
+
format!("\"{}\"", escaped)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
crate::KeyStyle::Single => {
|
|
172
|
+
let escaped = raw_value.replace('\'', "''");
|
|
173
|
+
|
|
174
|
+
format!("'{}'", escaped)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
crate::KeyStyle::Plain => raw_value,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if new_text != token.text() {
|
|
181
|
+
edits.push((token.text_range(), new_text));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if let Some(ref value_style) = enforcement.value_style {
|
|
190
|
+
if is_map_key(token) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let is_inline_scalar = matches!(
|
|
195
|
+
current_kind,
|
|
196
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if !is_inline_scalar {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if value_style.is_block_scalar() {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let target_kind = value_style.to_syntax_kind();
|
|
208
|
+
|
|
209
|
+
if current_kind == target_kind {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let raw_value = match current_kind {
|
|
214
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
215
|
+
let text = token.text();
|
|
216
|
+
unescape_double_quoted(&text[1..text.len() - 1])
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
220
|
+
let text = token.text();
|
|
221
|
+
unescape_single_quoted(&text[1..text.len() - 1])
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
225
|
+
|
|
226
|
+
_ => continue,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if current_kind == SyntaxKind::PLAIN_SCALAR && is_yaml_non_string(&raw_value) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let new_text = match value_style {
|
|
234
|
+
QuoteStyle::Double => {
|
|
235
|
+
if raw_value.contains('"') && current_kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let escaped = raw_value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
240
|
+
|
|
241
|
+
format!("\"{}\"", escaped)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
QuoteStyle::Single => {
|
|
245
|
+
let escaped = raw_value.replace('\'', "''");
|
|
246
|
+
format!("'{}'", escaped)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
QuoteStyle::Plain => {
|
|
250
|
+
if raw_value.contains('"') || raw_value.contains('\'') || raw_value.contains(':') || raw_value.contains('#') {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
raw_value
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_ => continue,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if new_text != token.text() {
|
|
261
|
+
edits.push((token.text_range(), new_text));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if edits.is_empty() {
|
|
269
|
+
return Ok(());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
edits.sort_by_key(|edit| std::cmp::Reverse(edit.0.start()));
|
|
273
|
+
|
|
274
|
+
let mut new_source = source;
|
|
275
|
+
|
|
276
|
+
for (range, replacement) in edits {
|
|
277
|
+
let start: usize = range.start().into();
|
|
278
|
+
let end: usize = range.end().into();
|
|
279
|
+
|
|
280
|
+
new_source.replace_range(start..end, &replacement);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let path = self.path.take();
|
|
284
|
+
*self = Self::parse(&new_source)?;
|
|
285
|
+
self.path = path;
|
|
286
|
+
|
|
287
|
+
Ok(())
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
pub fn enforce_collection_style(&mut self, style: &str, dot_path: Option<&str>) -> Result<(), YerbaError> {
|
|
291
|
+
let scope_path = dot_path.unwrap_or("");
|
|
292
|
+
|
|
293
|
+
if scope_path.contains("[]") {
|
|
294
|
+
let concrete_selectors = self.resolve_selectors(scope_path);
|
|
295
|
+
|
|
296
|
+
for selector in concrete_selectors.iter().rev() {
|
|
297
|
+
self.enforce_collection_style(style, Some(selector))?;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return Ok(());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
match style {
|
|
304
|
+
"block" | "flow" => {}
|
|
305
|
+
_ => {
|
|
306
|
+
return Err(YerbaError::ParseError(format!(
|
|
307
|
+
"unknown collection style '{}'. Valid options: flow, block",
|
|
308
|
+
style
|
|
309
|
+
)))
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if style == "block" {
|
|
314
|
+
let has_flow = self
|
|
315
|
+
.root
|
|
316
|
+
.descendants_with_tokens()
|
|
317
|
+
.any(|element| matches!(element.kind(), SyntaxKind::FLOW_SEQ | SyntaxKind::FLOW_MAP));
|
|
318
|
+
|
|
319
|
+
if !has_flow {
|
|
320
|
+
return Ok(());
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let value = match self.get_value(scope_path) {
|
|
325
|
+
Some(value) => value,
|
|
326
|
+
None => return Ok(()),
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
let mut selectors = Vec::new();
|
|
330
|
+
|
|
331
|
+
if !scope_path.is_empty() {
|
|
332
|
+
selectors.push(scope_path.to_string());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
collect_selectors(&value, scope_path, &mut selectors);
|
|
336
|
+
|
|
337
|
+
let mut collection_selectors: Vec<String> = selectors
|
|
338
|
+
.into_iter()
|
|
339
|
+
.filter(|selector| self.get_collection_style(selector).is_some_and(|current_style| current_style != style))
|
|
340
|
+
.collect();
|
|
341
|
+
|
|
342
|
+
collection_selectors.sort_by_key(|selector| std::cmp::Reverse(selector.len()));
|
|
343
|
+
|
|
344
|
+
for selector in &collection_selectors {
|
|
345
|
+
self.set_collection_style(selector, style)?;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Ok(())
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
pub fn enforce_sequence_indent(&mut self, style: &str, dot_path: Option<&str>) -> Result<(), YerbaError> {
|
|
352
|
+
let scope_path = dot_path.unwrap_or("");
|
|
353
|
+
|
|
354
|
+
if scope_path.contains("[]") {
|
|
355
|
+
let concrete_selectors = self.resolve_selectors(scope_path);
|
|
356
|
+
|
|
357
|
+
for selector in concrete_selectors.iter().rev() {
|
|
358
|
+
self.enforce_sequence_indent(style, Some(selector))?;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return Ok(());
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let scope_node = if scope_path.is_empty() {
|
|
365
|
+
self.root.clone()
|
|
366
|
+
} else {
|
|
367
|
+
match self.navigate(scope_path) {
|
|
368
|
+
Ok(node) => node,
|
|
369
|
+
Err(_) => return Ok(()),
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
let has_mismatches = scope_node.descendants().any(|descendant| {
|
|
374
|
+
if !BlockSeq::can_cast(descendant.kind()) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let sequence = match BlockSeq::cast(descendant.clone()) {
|
|
379
|
+
Some(sequence) => sequence,
|
|
380
|
+
None => return false,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
let first_entry = match sequence.entries().next() {
|
|
384
|
+
Some(entry) => entry,
|
|
385
|
+
None => return false,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
let parent_entry = match descendant.ancestors().find(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_ENTRY) {
|
|
389
|
+
Some(entry) => entry,
|
|
390
|
+
None => return false,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
let key_indent_length = preceding_whitespace_indent(&parent_entry).len();
|
|
394
|
+
let entry_indent_length = preceding_whitespace_indent(first_entry.syntax()).len();
|
|
395
|
+
let is_indented = entry_indent_length > key_indent_length;
|
|
396
|
+
|
|
397
|
+
match style {
|
|
398
|
+
"indented" => !is_indented,
|
|
399
|
+
"compact" => is_indented,
|
|
400
|
+
_ => false,
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if !has_mismatches {
|
|
405
|
+
return Ok(());
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
loop {
|
|
409
|
+
let value = match self.get_value(scope_path) {
|
|
410
|
+
Some(value) => value,
|
|
411
|
+
None => return Ok(()),
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
let mut selectors = Vec::new();
|
|
415
|
+
|
|
416
|
+
if !scope_path.is_empty() {
|
|
417
|
+
selectors.push(scope_path.to_string());
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
collect_selectors(&value, scope_path, &mut selectors);
|
|
421
|
+
|
|
422
|
+
let concrete_selectors: Vec<String> = selectors
|
|
423
|
+
.into_iter()
|
|
424
|
+
.flat_map(|selector| {
|
|
425
|
+
if selector.contains("[]") {
|
|
426
|
+
self.resolve_selectors(&selector)
|
|
427
|
+
} else {
|
|
428
|
+
vec![selector]
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
.collect();
|
|
432
|
+
|
|
433
|
+
let mut sequence_selectors: Vec<String> = concrete_selectors
|
|
434
|
+
.into_iter()
|
|
435
|
+
.filter(|selector| self.get_sequence_indent(selector).is_some_and(|current_style| current_style != style))
|
|
436
|
+
.collect();
|
|
437
|
+
|
|
438
|
+
if sequence_selectors.is_empty() {
|
|
439
|
+
return Ok(());
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
sequence_selectors.sort_by_key(|selector| std::cmp::Reverse(selector.len()));
|
|
443
|
+
|
|
444
|
+
self.set_sequence_indent(&sequence_selectors[0], style)?;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
pub fn set_sequence_indent(&mut self, dot_path: &str, style: &str) -> Result<(), YerbaError> {
|
|
449
|
+
let current_style = self
|
|
450
|
+
.get_sequence_indent(dot_path)
|
|
451
|
+
.ok_or_else(|| YerbaError::ParseError(format!("'{}' is not a block sequence", dot_path)))?;
|
|
452
|
+
|
|
453
|
+
if current_style == style {
|
|
454
|
+
return Ok(());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if style != "indented" && style != "compact" {
|
|
458
|
+
return Err(YerbaError::ParseError(format!(
|
|
459
|
+
"unknown sequence indent style '{}'. Valid options: compact, indented",
|
|
460
|
+
style
|
|
461
|
+
)));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let current_node = self.navigate(dot_path)?;
|
|
465
|
+
let source = self.to_string();
|
|
466
|
+
|
|
467
|
+
let entry_node = current_node
|
|
468
|
+
.ancestors()
|
|
469
|
+
.find(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_ENTRY)
|
|
470
|
+
.ok_or_else(|| YerbaError::ParseError("could not find parent map entry".to_string()))?;
|
|
471
|
+
|
|
472
|
+
let entry_start: usize = entry_node.text_range().start().into();
|
|
473
|
+
let line_start = source[..entry_start].rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
474
|
+
let key_indent_length = entry_start - line_start;
|
|
475
|
+
|
|
476
|
+
let sequence = current_node
|
|
477
|
+
.descendants()
|
|
478
|
+
.find_map(BlockSeq::cast)
|
|
479
|
+
.ok_or_else(|| YerbaError::ParseError("could not find block sequence".to_string()))?;
|
|
480
|
+
|
|
481
|
+
let first_entry = sequence.entries().next();
|
|
482
|
+
|
|
483
|
+
if first_entry.is_none() {
|
|
484
|
+
return Ok(());
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let current_entry_indent_length = preceding_whitespace_indent(first_entry.unwrap().syntax()).len();
|
|
488
|
+
|
|
489
|
+
let indent_diff: i32 = match style {
|
|
490
|
+
"indented" => (key_indent_length as i32 + 2) - current_entry_indent_length as i32,
|
|
491
|
+
"compact" => key_indent_length as i32 - current_entry_indent_length as i32,
|
|
492
|
+
_ => unreachable!(),
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
if indent_diff == 0 {
|
|
496
|
+
return Ok(());
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let sequence_range = sequence.syntax().text_range();
|
|
500
|
+
let sequence_start: usize = sequence_range.start().into();
|
|
501
|
+
let sequence_end: usize = sequence_range.end().into();
|
|
502
|
+
|
|
503
|
+
let before_sequence = &source[..sequence_start];
|
|
504
|
+
let line_start_of_first_entry = before_sequence.rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
505
|
+
let full_text = &source[line_start_of_first_entry..sequence_end];
|
|
506
|
+
|
|
507
|
+
let reindented: String = full_text
|
|
508
|
+
.lines()
|
|
509
|
+
.map(|line| {
|
|
510
|
+
if line.trim().is_empty() {
|
|
511
|
+
String::new()
|
|
512
|
+
} else {
|
|
513
|
+
let current_line_indent = line.len() - line.trim_start().len();
|
|
514
|
+
let new_indent = (current_line_indent as i32 + indent_diff).max(0) as usize;
|
|
515
|
+
format!("{}{}", " ".repeat(new_indent), line.trim_start())
|
|
516
|
+
}
|
|
517
|
+
})
|
|
518
|
+
.collect::<Vec<_>>()
|
|
519
|
+
.join("\n");
|
|
520
|
+
|
|
521
|
+
let replace_range = TextRange::new(rowan::TextSize::from(line_start_of_first_entry as u32), sequence_range.end());
|
|
522
|
+
|
|
523
|
+
self.apply_edit(replace_range, &reindented)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
pub fn set_collection_style(&mut self, dot_path: &str, style: &str) -> Result<(), YerbaError> {
|
|
527
|
+
let current_style = self
|
|
528
|
+
.get_collection_style(dot_path)
|
|
529
|
+
.ok_or_else(|| YerbaError::ParseError(format!("'{}' is not a collection (sequence or map)", dot_path)))?;
|
|
530
|
+
|
|
531
|
+
if current_style == style {
|
|
532
|
+
return Ok(());
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let current_node = self.navigate(dot_path)?;
|
|
536
|
+
let value = node_to_yaml_value(¤t_node);
|
|
537
|
+
|
|
538
|
+
match &value {
|
|
539
|
+
yaml_serde::Value::Sequence(sequence) if sequence.is_empty() => return Ok(()),
|
|
540
|
+
yaml_serde::Value::Mapping(mapping) if mapping.is_empty() => return Ok(()),
|
|
541
|
+
_ => {}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let source = self.to_string();
|
|
545
|
+
|
|
546
|
+
let entry_node = current_node.ancestors().find(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_ENTRY);
|
|
547
|
+
|
|
548
|
+
let (key_indent, colon_in_line) = if let Some(ref entry) = entry_node {
|
|
549
|
+
let entry_start: usize = entry.text_range().start().into();
|
|
550
|
+
let entry_line_start = source[..entry_start].rfind('\n').map(|position| position + 1).unwrap_or(0);
|
|
551
|
+
let indent = entry_start - entry_line_start;
|
|
552
|
+
|
|
553
|
+
let entry_text = entry.text().to_string();
|
|
554
|
+
let colon_offset = entry_text.find(':').map(|offset| entry_start + offset);
|
|
555
|
+
|
|
556
|
+
(indent, colon_offset)
|
|
557
|
+
} else {
|
|
558
|
+
(0, None)
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
match style {
|
|
562
|
+
"flow" => {
|
|
563
|
+
let flow_text = crate::yaml_writer::yaml_value_to_flow_text(&value);
|
|
564
|
+
|
|
565
|
+
let collection_node = current_node
|
|
566
|
+
.descendants()
|
|
567
|
+
.find(|descendant| descendant.kind() == SyntaxKind::BLOCK_SEQ || descendant.kind() == SyntaxKind::BLOCK_MAP)
|
|
568
|
+
.ok_or_else(|| YerbaError::ParseError("could not find block collection node".to_string()))?;
|
|
569
|
+
|
|
570
|
+
let block_end: usize = collection_node.text_range().end().into();
|
|
571
|
+
|
|
572
|
+
if let Some(colon_position) = colon_in_line {
|
|
573
|
+
let replace_range = TextRange::new(rowan::TextSize::from((colon_position + 1) as u32), rowan::TextSize::from(block_end as u32));
|
|
574
|
+
|
|
575
|
+
self.apply_edit(replace_range, &format!(" {}", flow_text))
|
|
576
|
+
} else {
|
|
577
|
+
let replace_range = collection_node.text_range();
|
|
578
|
+
|
|
579
|
+
self.apply_edit(replace_range, &flow_text)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
"block" => {
|
|
584
|
+
let flow_node = current_node
|
|
585
|
+
.descendants()
|
|
586
|
+
.find(|descendant| descendant.kind() == SyntaxKind::FLOW_SEQ || descendant.kind() == SyntaxKind::FLOW_MAP)
|
|
587
|
+
.ok_or_else(|| YerbaError::ParseError("could not find flow collection node".to_string()))?;
|
|
588
|
+
|
|
589
|
+
let flow_end: usize = flow_node.text_range().end().into();
|
|
590
|
+
let value_indent = key_indent + 2;
|
|
591
|
+
let block_text = crate::yaml_writer::yaml_value_to_block_text(&value, value_indent);
|
|
592
|
+
|
|
593
|
+
if let Some(colon_position) = colon_in_line {
|
|
594
|
+
let replace_range = TextRange::new(rowan::TextSize::from((colon_position + 1) as u32), rowan::TextSize::from(flow_end as u32));
|
|
595
|
+
|
|
596
|
+
self.apply_edit(replace_range, &format!("\n{}", block_text))
|
|
597
|
+
} else {
|
|
598
|
+
let replace_range = flow_node.text_range();
|
|
599
|
+
|
|
600
|
+
self.apply_edit(replace_range, &format!("\n{}", block_text))
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_ => Err(YerbaError::ParseError(format!(
|
|
605
|
+
"unknown collection style '{}'. Valid options: flow, block",
|
|
606
|
+
style
|
|
607
|
+
))),
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
4
611
|
pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
|
|
5
612
|
let nodes = if dot_path.contains('[') {
|
|
6
613
|
self.navigate_all_compact(dot_path)
|
|
@@ -363,7 +970,7 @@ impl Document {
|
|
|
363
970
|
_ => continue,
|
|
364
971
|
};
|
|
365
972
|
|
|
366
|
-
if
|
|
973
|
+
if is_yaml_non_string(&raw_value) {
|
|
367
974
|
continue;
|
|
368
975
|
}
|
|
369
976
|
|
|
@@ -413,7 +1020,7 @@ impl Document {
|
|
|
413
1020
|
_ => continue,
|
|
414
1021
|
};
|
|
415
1022
|
|
|
416
|
-
if is_yaml_non_string(&raw_value) {
|
|
1023
|
+
if current_kind == SyntaxKind::PLAIN_SCALAR && is_yaml_non_string(&raw_value) {
|
|
417
1024
|
continue;
|
|
418
1025
|
}
|
|
419
1026
|
|
data/rust/src/ffi.rs
CHANGED
|
@@ -351,6 +351,52 @@ pub unsafe extern "C" fn yerba_document_set_quote_style(document: *mut Document,
|
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
#[no_mangle]
|
|
355
|
+
pub unsafe extern "C" fn yerba_document_get_collection_style(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
356
|
+
let document = &*document;
|
|
357
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
358
|
+
|
|
359
|
+
match document.get_collection_style(selector_string) {
|
|
360
|
+
Some(style) => CString::new(style).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut()),
|
|
361
|
+
None => std::ptr::null_mut(),
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[no_mangle]
|
|
366
|
+
pub unsafe extern "C" fn yerba_document_set_collection_style(document: *mut Document, path: *const c_char, style: *const c_char) -> YerbaResult {
|
|
367
|
+
let document = &mut *document;
|
|
368
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
369
|
+
let style_string = CStr::from_ptr(style).to_str().unwrap_or("");
|
|
370
|
+
|
|
371
|
+
match document.set_collection_style(selector_string, style_string) {
|
|
372
|
+
Ok(()) => YerbaResult::ok(),
|
|
373
|
+
Err(e) => YerbaResult::err(&e.to_string()),
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#[no_mangle]
|
|
378
|
+
pub unsafe extern "C" fn yerba_document_get_sequence_indent(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
379
|
+
let document = &*document;
|
|
380
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
381
|
+
|
|
382
|
+
match document.get_sequence_indent(selector_string) {
|
|
383
|
+
Some(style) => CString::new(style).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut()),
|
|
384
|
+
None => std::ptr::null_mut(),
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
#[no_mangle]
|
|
389
|
+
pub unsafe extern "C" fn yerba_document_set_sequence_indent(document: *mut Document, path: *const c_char, style: *const c_char) -> YerbaResult {
|
|
390
|
+
let document = &mut *document;
|
|
391
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
392
|
+
let style_string = CStr::from_ptr(style).to_str().unwrap_or("");
|
|
393
|
+
|
|
394
|
+
match document.set_sequence_indent(selector_string, style_string) {
|
|
395
|
+
Ok(()) => YerbaResult::ok(),
|
|
396
|
+
Err(e) => YerbaResult::err(&e.to_string()),
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
354
400
|
#[no_mangle]
|
|
355
401
|
pub unsafe extern "C" fn yerba_document_evaluate_condition(document: *const Document, parent_path: *const c_char, condition: *const c_char) -> bool {
|
|
356
402
|
let document = &*document;
|
data/rust/src/lib.rs
CHANGED
|
@@ -10,6 +10,7 @@ mod syntax;
|
|
|
10
10
|
mod yaml_writer;
|
|
11
11
|
pub mod yerbafile;
|
|
12
12
|
|
|
13
|
+
pub use document::style::StyleEnforcement;
|
|
13
14
|
pub use document::{collect_selectors, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField};
|
|
14
15
|
pub use error::YerbaError;
|
|
15
16
|
pub use quote_style::{KeyStyle, QuoteStyle};
|