yerba 0.5.0-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.
@@ -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(&current_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 raw_value.is_empty() || is_yaml_non_string(&raw_value) {
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;