yerba 0.8.0-arm-linux-gnu → 0.9.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +170 -19
  3. data/exe/arm-linux-gnu/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +7 -0
  5. data/ext/yerba/yerba.c +54 -5
  6. data/lib/yerba/3.2/yerba.so +0 -0
  7. data/lib/yerba/3.3/yerba.so +0 -0
  8. data/lib/yerba/3.4/yerba.so +0 -0
  9. data/lib/yerba/4.0/yerba.so +0 -0
  10. data/lib/yerba/formatting.rb +9 -13
  11. data/lib/yerba/map.rb +11 -8
  12. data/lib/yerba/sequence.rb +2 -7
  13. data/lib/yerba/version.rb +1 -1
  14. data/rust/Cargo.lock +2 -342
  15. data/rust/Cargo.toml +15 -6
  16. data/rust/src/commands/blank_lines.rs +23 -3
  17. data/rust/src/commands/directives.rs +4 -4
  18. data/rust/src/commands/get.rs +7 -10
  19. data/rust/src/commands/init.rs +10 -4
  20. data/rust/src/commands/insert.rs +18 -12
  21. data/rust/src/commands/location.rs +2 -2
  22. data/rust/src/commands/mate.rs +7 -7
  23. data/rust/src/commands/mod.rs +118 -90
  24. data/rust/src/commands/move_key.rs +9 -6
  25. data/rust/src/commands/quote_style.rs +4 -5
  26. data/rust/src/commands/schema.rs +6 -6
  27. data/rust/src/commands/selectors.rs +2 -2
  28. data/rust/src/commands/set.rs +18 -7
  29. data/rust/src/commands/sort.rs +36 -42
  30. data/rust/src/commands/sort_keys.rs +2 -2
  31. data/rust/src/commands/ui.rs +156 -0
  32. data/rust/src/commands/unique.rs +7 -7
  33. data/rust/src/commands/version.rs +13 -3
  34. data/rust/src/document/insert.rs +25 -9
  35. data/rust/src/document/mod.rs +7 -3
  36. data/rust/src/document/set.rs +115 -33
  37. data/rust/src/document/style.rs +131 -11
  38. data/rust/src/error.rs +27 -0
  39. data/rust/src/ffi.rs +55 -12
  40. data/rust/src/lib.rs +12 -2
  41. data/rust/src/quote_style.rs +12 -11
  42. data/rust/src/syntax.rs +285 -11
  43. data/rust/src/validation.rs +30 -0
  44. data/rust/src/yaml_writer.rs +9 -21
  45. data/rust/src/yerbafile.rs +76 -4
  46. metadata +4 -2
@@ -1,5 +1,23 @@
1
1
  use super::*;
2
2
 
3
+ fn is_plain_writable(value: &str) -> bool {
4
+ crate::syntax::is_plain_safe(value) && !crate::syntax::is_yaml_non_string(value)
5
+ }
6
+
7
+ fn scalar_replacement_text(value: &str, kind: SyntaxKind) -> String {
8
+ let representable = match kind {
9
+ SyntaxKind::PLAIN_SCALAR => is_plain_writable(value),
10
+ SyntaxKind::SINGLE_QUOTED_SCALAR => crate::syntax::is_single_quotable(value),
11
+ _ => true,
12
+ };
13
+
14
+ if !representable {
15
+ return format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR);
16
+ }
17
+
18
+ format_scalar_value(value, kind)
19
+ }
20
+
3
21
  fn holds_collection(node: &SyntaxNode) -> bool {
4
22
  node.descendants().any(|descendant| {
5
23
  matches!(
@@ -54,6 +72,34 @@ fn value_span(node: &SyntaxNode) -> Option<ValueSpan> {
54
72
  })
55
73
  }
56
74
 
75
+ fn scalar_edit(source: &str, node: &SyntaxNode, value: &str, plain: bool) -> Option<(TextRange, String)> {
76
+ if let Some(block_scalar) = node.descendants().find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR) {
77
+ let replacement = if plain {
78
+ value.to_string()
79
+ } else {
80
+ Document::block_scalar_replacement(source, &block_scalar, value)
81
+ };
82
+
83
+ return Some((block_scalar.text_range(), replacement));
84
+ }
85
+
86
+ if holds_collection(node) {
87
+ let replacement = if plain { value.to_string() } else { crate::syntax::quote_scalar(value) };
88
+
89
+ return value_span(node).map(|span| (span.range, replacement_text(&span, &replacement)));
90
+ }
91
+
92
+ let scalar_token = find_scalar_token(node)?;
93
+
94
+ let replacement = if plain {
95
+ value.to_string()
96
+ } else {
97
+ scalar_replacement_text(value, scalar_token.kind())
98
+ };
99
+
100
+ Some((scalar_token.text_range(), replacement))
101
+ }
102
+
57
103
  fn replacement_text(span: &ValueSpan, value: &str) -> String {
58
104
  let mut text = if span.separated { format!(" {}", value) } else { value.to_string() };
59
105
 
@@ -67,51 +113,70 @@ fn replacement_text(span: &ValueSpan, value: &str) -> String {
67
113
 
68
114
  impl Document {
69
115
  pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
70
- let current_node = self.navigate(dot_path)?;
116
+ self.set_with(dot_path, value, false)
117
+ }
71
118
 
72
- if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
73
- let source = self.source_text();
74
- let new_text = Self::block_scalar_replacement(&source, &block_scalar, value);
119
+ pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
120
+ self.set_all_with(dot_path, value, false)
121
+ }
75
122
 
76
- return self.apply_edit(block_scalar.text_range(), &new_text);
77
- }
123
+ pub fn set_all_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
124
+ self.set_all_with(dot_path, value, true)
125
+ }
78
126
 
79
- if holds_collection(&current_node) {
80
- let span = value_span(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
127
+ fn set_all_with(&mut self, dot_path: &str, value: &str, plain: bool) -> Result<(), YerbaError> {
128
+ let nodes = self.navigate_all_compact(dot_path);
81
129
 
82
- return self.apply_edit(span.range, &replacement_text(&span, value));
130
+ if nodes.is_empty() {
131
+ return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
83
132
  }
84
133
 
85
- let scalar_token = find_scalar_token(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
86
-
87
- let new_text = format_scalar_value(value, scalar_token.kind());
134
+ let source = self.source_text();
135
+ let edits = nodes.iter().filter_map(|node| scalar_edit(&source, node, value, plain)).collect();
88
136
 
89
- self.replace_token(&scalar_token, &new_text)
137
+ self.apply_edits(edits)
90
138
  }
91
139
 
92
- pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
93
- let nodes = self.navigate_all_compact(dot_path);
140
+ pub fn set_where(&mut self, container_path: &str, relative_path: &str, value: &str, condition: &str, all: bool, plain: bool) -> Result<usize, YerbaError> {
141
+ validate_item_condition(condition)?;
94
142
 
95
- if nodes.is_empty() {
96
- return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
143
+ let items = self.navigate_all_compact(container_path);
144
+
145
+ if items.is_empty() {
146
+ return Err(YerbaError::SelectorNotFound(container_path.to_string()));
97
147
  }
98
148
 
99
149
  let source = self.source_text();
100
- let mut edits: Vec<(TextRange, String)> = Vec::new();
101
-
102
- for node in nodes {
103
- if let Some(block_scalar) = node.descendants().find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR) {
104
- edits.push((block_scalar.text_range(), Self::block_scalar_replacement(&source, &block_scalar, value)));
105
- } else if holds_collection(&node) {
106
- if let Some(span) = value_span(&node) {
107
- edits.push((span.range, replacement_text(&span, value)));
108
- }
109
- } else if let Some(scalar_token) = find_scalar_token(&node) {
110
- edits.push((scalar_token.text_range(), format_scalar_value(value, scalar_token.kind())));
150
+ let mut targets: Vec<SyntaxNode> = Vec::new();
151
+
152
+ for item in &items {
153
+ if !self.evaluate_condition_on_node(item, condition) {
154
+ continue;
111
155
  }
156
+
157
+ targets.extend(navigate_from_node(item, relative_path));
112
158
  }
113
159
 
114
- self.apply_edits(edits)
160
+ if targets.is_empty() {
161
+ return Ok(0);
162
+ }
163
+
164
+ if !all && targets.len() > 1 {
165
+ let selector = if container_path.is_empty() {
166
+ relative_path.to_string()
167
+ } else {
168
+ format!("{}.{}", container_path, relative_path)
169
+ };
170
+
171
+ return Err(YerbaError::AmbiguousSelector(selector, targets.len()));
172
+ }
173
+
174
+ let edits: Vec<(TextRange, String)> = targets.iter().filter_map(|node| scalar_edit(&source, node, value, plain)).collect();
175
+ let count = edits.len();
176
+
177
+ self.apply_edits(edits)?;
178
+
179
+ Ok(count)
115
180
  }
116
181
 
117
182
  pub fn set_scalar_style(&mut self, dot_path: &str, style: &QuoteStyle) -> Result<(), YerbaError> {
@@ -136,22 +201,39 @@ impl Document {
136
201
  }
137
202
 
138
203
  pub fn set_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
204
+ self.set_with(dot_path, value, true)
205
+ }
206
+
207
+ fn set_with(&mut self, dot_path: &str, value: &str, plain: bool) -> Result<(), YerbaError> {
139
208
  let current_node = self.navigate(dot_path)?;
140
209
 
141
210
  if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
142
- let range = block_scalar.text_range();
143
- return self.apply_edit(range, value);
211
+ if plain {
212
+ return self.apply_edit(block_scalar.text_range(), value);
213
+ }
214
+
215
+ let source = self.source_text();
216
+ let new_text = Self::block_scalar_replacement(&source, &block_scalar, value);
217
+
218
+ return self.apply_edit(block_scalar.text_range(), &new_text);
144
219
  }
145
220
 
146
221
  if holds_collection(&current_node) {
147
222
  let span = value_span(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
223
+ let replacement = if plain { value.to_string() } else { crate::syntax::quote_scalar(value) };
148
224
 
149
- return self.apply_edit(span.range, &replacement_text(&span, value));
225
+ return self.apply_edit(span.range, &replacement_text(&span, &replacement));
150
226
  }
151
227
 
152
228
  let scalar_token = find_scalar_token(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
153
229
 
154
- self.replace_token(&scalar_token, value)
230
+ if plain {
231
+ return self.replace_token(&scalar_token, value);
232
+ }
233
+
234
+ let new_text = scalar_replacement_text(value, scalar_token.kind());
235
+
236
+ self.replace_token(&scalar_token, &new_text)
155
237
  }
156
238
 
157
239
  fn block_scalar_replacement(source: &str, block_scalar: &SyntaxNode, value: &str) -> String {
@@ -7,6 +7,13 @@ pub struct StyleEnforcement {
7
7
  pub value_style: Option<QuoteStyle>,
8
8
  }
9
9
 
10
+ #[derive(Debug, Clone, Default)]
11
+ pub struct BlankLineOptions {
12
+ pub before: Vec<String>,
13
+ pub after: Vec<String>,
14
+ pub skip_empty: bool,
15
+ }
16
+
10
17
  impl Document {
11
18
  pub fn enforce_styles(&mut self, enforcement: &StyleEnforcement) -> Result<(), YerbaError> {
12
19
  let source = self.source_text();
@@ -588,6 +595,10 @@ impl Document {
588
595
  }
589
596
 
590
597
  pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
598
+ self.enforce_blank_lines_with(dot_path, blank_lines, &BlankLineOptions::default())
599
+ }
600
+
601
+ pub fn enforce_blank_lines_with(&mut self, dot_path: &str, blank_lines: usize, options: &BlankLineOptions) -> Result<(), YerbaError> {
591
602
  let nodes = if dot_path.contains('[') {
592
603
  self.navigate_all_compact(dot_path)
593
604
  } else {
@@ -597,14 +608,44 @@ impl Document {
597
608
  let mut edits: Vec<(TextRange, String)> = Vec::new();
598
609
 
599
610
  for current_node in &nodes {
600
- let entry_nodes: Vec<SyntaxNode> = match first_collection(current_node) {
601
- Some(FirstCollection::Sequence(sequence)) => sequence.entries().map(|entry| entry.syntax().clone()).collect(),
602
- Some(FirstCollection::Map(map)) => map.entries().map(|entry| entry.syntax().clone()).collect(),
611
+ let entries: Vec<BlankLineEntry> = match first_collection(current_node) {
612
+ Some(FirstCollection::Sequence(sequence)) => sequence
613
+ .entries()
614
+ .map(|entry| BlankLineEntry {
615
+ is_empty: is_empty_value(&node_to_yaml_value(entry.syntax())),
616
+ node: entry.syntax().clone(),
617
+ key: None,
618
+ })
619
+ .collect(),
620
+
621
+ Some(FirstCollection::Map(map)) => map
622
+ .entries()
623
+ .map(|entry| {
624
+ let value = entry
625
+ .value()
626
+ .map(|value_node| node_to_yaml_value(value_node.syntax()))
627
+ .unwrap_or(yaml_serde::Value::Null);
628
+
629
+ BlankLineEntry {
630
+ key: entry.key().and_then(|key_node| extract_scalar_text(key_node.syntax())),
631
+ is_empty: is_empty_value(&value),
632
+ node: entry.syntax().clone(),
633
+ }
634
+ })
635
+ .collect(),
636
+
603
637
  None => continue,
604
638
  };
605
639
 
606
- for entry_node in entry_nodes.iter().skip(1) {
607
- collect_blank_line_edits(entry_node, blank_lines, &mut edits);
640
+ for index in 1..entries.len() {
641
+ let entry = &entries[index];
642
+ let previous = &entries[index - 1];
643
+
644
+ if !should_adjust_gap(entry, previous, options) {
645
+ continue;
646
+ }
647
+
648
+ collect_blank_line_edits(&entry.node, blank_lines, &mut edits);
608
649
  }
609
650
  }
610
651
 
@@ -738,6 +779,41 @@ impl Document {
738
779
  self.enforce_quotes_at(style, None)
739
780
  }
740
781
 
782
+ pub fn decode_escapes(&mut self, dot_path: Option<&str>) -> Result<usize, YerbaError> {
783
+ let scope_ranges: Vec<TextRange> = match dot_path {
784
+ Some(path) if !path.is_empty() => self.navigate_all_compact(path).iter().map(|node| node.text_range()).collect(),
785
+ _ => vec![self.root.text_range()],
786
+ };
787
+
788
+ let mut edits: Vec<(TextRange, String)> = Vec::new();
789
+
790
+ for element in self.root.descendants_with_tokens() {
791
+ let Some(token) = element.into_token() else { continue };
792
+
793
+ if token.kind() != SyntaxKind::DOUBLE_QUOTED_SCALAR {
794
+ continue;
795
+ }
796
+
797
+ if !scope_ranges.iter().any(|range| range.contains_range(token.text_range())) {
798
+ continue;
799
+ }
800
+
801
+ let Some(value) = raw_scalar_value(&token) else { continue };
802
+
803
+ let rewritten = format_scalar_value(&value, SyntaxKind::DOUBLE_QUOTED_SCALAR);
804
+
805
+ if rewritten != token.text() {
806
+ edits.push((token.text_range(), rewritten));
807
+ }
808
+ }
809
+
810
+ let count = edits.len();
811
+
812
+ self.apply_edits(edits)?;
813
+
814
+ Ok(count)
815
+ }
816
+
741
817
  pub fn enforce_quotes_at(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<Vec<String>, YerbaError> {
742
818
  let source = self.source_text();
743
819
 
@@ -819,11 +895,7 @@ impl Document {
819
895
  let is_multiline = trimmed.contains('\n');
820
896
 
821
897
  let new_text = match style {
822
- QuoteStyle::Double => {
823
- let escaped = trimmed.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n");
824
-
825
- format!("\"{}\"", escaped)
826
- }
898
+ QuoteStyle::Double => format_scalar_value(trimmed, SyntaxKind::DOUBLE_QUOTED_SCALAR),
827
899
 
828
900
  QuoteStyle::Single => {
829
901
  if is_multiline {
@@ -879,7 +951,7 @@ impl Document {
879
951
 
880
952
  let offset: usize = token.text_range().start().into();
881
953
  let line_prefix = &source[line_start_at(&source, offset)..offset];
882
- let indent = line_prefix.len() - line_prefix.trim_start().len() + 2;
954
+ let indent = block_scalar_indent(line_prefix);
883
955
  let indent_str = " ".repeat(indent);
884
956
  let header = style.block_header();
885
957
 
@@ -932,6 +1004,54 @@ impl Document {
932
1004
  }
933
1005
  }
934
1006
 
1007
+ struct BlankLineEntry {
1008
+ node: SyntaxNode,
1009
+ key: Option<String>,
1010
+ is_empty: bool,
1011
+ }
1012
+
1013
+ fn should_adjust_gap(entry: &BlankLineEntry, previous: &BlankLineEntry, options: &BlankLineOptions) -> bool {
1014
+ let usable = |candidate: &BlankLineEntry| !options.skip_empty || !candidate.is_empty;
1015
+
1016
+ if options.before.is_empty() && options.after.is_empty() {
1017
+ return usable(entry);
1018
+ }
1019
+
1020
+ (matches_key(entry.key.as_deref(), &options.before) && usable(entry)) || (matches_key(previous.key.as_deref(), &options.after) && usable(previous))
1021
+ }
1022
+
1023
+ fn matches_key(key: Option<&str>, keys: &[String]) -> bool {
1024
+ match key {
1025
+ Some(key) => keys.iter().any(|wanted| wanted == key),
1026
+ None => false,
1027
+ }
1028
+ }
1029
+
1030
+ fn is_empty_value(value: &yaml_serde::Value) -> bool {
1031
+ match value {
1032
+ yaml_serde::Value::Null => true,
1033
+ yaml_serde::Value::String(text) => text.is_empty(),
1034
+ yaml_serde::Value::Sequence(entries) => entries.is_empty(),
1035
+ yaml_serde::Value::Mapping(entries) => entries.is_empty(),
1036
+ _ => false,
1037
+ }
1038
+ }
1039
+
1040
+ fn block_scalar_indent(line_prefix: &str) -> usize {
1041
+ let mut column = 0;
1042
+ let bytes = line_prefix.as_bytes();
1043
+
1044
+ while column < bytes.len() {
1045
+ match bytes[column] {
1046
+ b' ' => column += 1,
1047
+ b'-' if bytes.get(column + 1) == Some(&b' ') => column += 2,
1048
+ _ => break,
1049
+ }
1050
+ }
1051
+
1052
+ column + 2
1053
+ }
1054
+
935
1055
  fn inline_quote_replacement(raw_value: &str, current_kind: SyntaxKind, style: &QuoteStyle) -> Option<String> {
936
1056
  match style {
937
1057
  QuoteStyle::Double => {
data/rust/src/error.rs CHANGED
@@ -10,6 +10,8 @@ pub enum YerbaError {
10
10
  IndexOutOfBounds(usize, usize),
11
11
  UnknownKeys(Vec<String>),
12
12
  DuplicateValues(Vec<crate::DuplicateInfo>),
13
+ ControlCharacters(Vec<crate::ControlCharacter>),
14
+ #[cfg(feature = "schema")]
13
15
  SchemaValidation(Vec<crate::schema::ValidationError>),
14
16
  DuplicateKey {
15
17
  key: String,
@@ -74,10 +76,24 @@ impl std::fmt::Display for YerbaError {
74
76
  write!(f, "found {} {}: {}", duplicates.len(), noun, details.join(", "))
75
77
  }
76
78
 
79
+ YerbaError::ControlCharacters(found) => {
80
+ let noun = if found.len() == 1 { "character" } else { "characters" };
81
+ let details: Vec<String> = found.iter().map(|control| format!(" {}", control)).collect();
82
+
83
+ write!(
84
+ f,
85
+ "found {} control {} that YAML parsers reject:\n{}\n\n Control characters must be removed, or escaped as \\xNN inside a double-quoted value.\n",
86
+ found.len(),
87
+ noun,
88
+ details.join("\n")
89
+ )
90
+ }
91
+
77
92
  YerbaError::IndexOutOfBounds(index, length) => {
78
93
  write!(f, "index {} out of bounds (length {})", index, length)
79
94
  }
80
95
 
96
+ #[cfg(feature = "schema")]
81
97
  YerbaError::SchemaValidation(errors) => {
82
98
  let details: Vec<String> = errors.iter().map(|error| error.to_string()).collect();
83
99
 
@@ -137,6 +153,7 @@ impl GitHubAnnotations for YerbaError {
137
153
  })
138
154
  .collect(),
139
155
 
156
+ #[cfg(feature = "schema")]
140
157
  YerbaError::SchemaValidation(errors) => errors
141
158
  .iter()
142
159
  .map(|error| GitHubAnnotation {
@@ -147,6 +164,16 @@ impl GitHubAnnotations for YerbaError {
147
164
  })
148
165
  .collect(),
149
166
 
167
+ YerbaError::ControlCharacters(found) => found
168
+ .iter()
169
+ .map(|control| GitHubAnnotation {
170
+ level: "error ",
171
+ file: file.to_string(),
172
+ line: Some(control.line),
173
+ message: format!("control character U+{:04X} at column {}", control.character as u32, control.column),
174
+ })
175
+ .collect(),
176
+
150
177
  YerbaError::DuplicateKey { key, duplicate_line, .. } => vec![GitHubAnnotation {
151
178
  level: "error ",
152
179
  file: file.to_string(),
data/rust/src/ffi.rs CHANGED
@@ -485,25 +485,34 @@ pub unsafe extern "C" fn yerba_document_find(document: *const Document, path: *c
485
485
  CString::new(json).unwrap_or_default().into_raw()
486
486
  }
487
487
 
488
+ unsafe fn borrow_value<'a>(value: *const c_char, length: usize) -> &'a str {
489
+ if value.is_null() {
490
+ return "";
491
+ }
492
+
493
+ std::str::from_utf8(std::slice::from_raw_parts(value as *const u8, length)).unwrap_or("")
494
+ }
495
+
488
496
  #[no_mangle]
489
497
  pub unsafe extern "C" fn yerba_document_set(
490
498
  document: *mut Document,
491
499
  path: *const c_char,
492
500
  value: *const c_char,
501
+ value_length: usize,
493
502
  value_type: YerbaValueType,
503
+ plain: bool,
494
504
  all: bool,
495
505
  ) -> YerbaResult {
496
506
  let document = &mut *document;
497
507
  let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
498
- let value_string = CStr::from_ptr(value).to_str().unwrap_or("");
499
-
500
- let result = if all {
501
- document.set_all(selector_string, value_string)
502
- } else {
503
- match value_type {
504
- YerbaValueType::String => document.set(selector_string, value_string),
505
- _ => document.set_plain(selector_string, value_string),
506
- }
508
+ let value_string = borrow_value(value, value_length);
509
+ let verbatim = plain || value_type != YerbaValueType::String;
510
+
511
+ let result = match (all, verbatim) {
512
+ (true, false) => document.set_all(selector_string, value_string),
513
+ (true, true) => document.set_all_plain(selector_string, value_string),
514
+ (false, false) => document.set(selector_string, value_string),
515
+ (false, true) => document.set_plain(selector_string, value_string),
507
516
  };
508
517
 
509
518
  match result {
@@ -517,17 +526,25 @@ pub unsafe extern "C" fn yerba_document_insert(
517
526
  document: *mut Document,
518
527
  path: *const c_char,
519
528
  value: *const c_char,
529
+ value_length: usize,
520
530
  value_type: YerbaValueType,
531
+ plain: bool,
532
+ style: *const c_char,
521
533
  before: *const c_char,
522
534
  after: *const c_char,
523
535
  at: i64,
524
536
  ) -> YerbaResult {
525
537
  let document = &mut *document;
526
538
  let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
527
- let raw_value = CStr::from_ptr(value).to_str().unwrap_or("");
539
+ let raw_value = borrow_value(value, value_length);
528
540
 
529
541
  let value_string = match value_type {
530
- YerbaValueType::String => crate::syntax::quote_if_needed(raw_value),
542
+ YerbaValueType::String if !plain => {
543
+ let style = if style.is_null() { None } else { CStr::from_ptr(style).to_str().ok() };
544
+
545
+ crate::syntax::quote_scalar_styled(raw_value, style)
546
+ }
547
+
531
548
  _ => raw_value.to_string(),
532
549
  };
533
550
 
@@ -543,7 +560,13 @@ pub unsafe extern "C" fn yerba_document_insert(
543
560
  InsertPosition::Last
544
561
  };
545
562
 
546
- match document.insert_into(selector_string, &value_string, position) {
563
+ let result = if plain {
564
+ document.insert_fragment_into(selector_string, &value_string, position)
565
+ } else {
566
+ document.insert_into(selector_string, &value_string, position)
567
+ };
568
+
569
+ match result {
547
570
  Ok(()) => YerbaResult::ok(),
548
571
  Err(e) => YerbaResult::err(&e.to_string()),
549
572
  }
@@ -746,6 +769,7 @@ pub unsafe extern "C" fn yerba_document_blank_lines(document: *mut Document, pat
746
769
  }
747
770
 
748
771
  /// Caller must free with yerba_string_free.
772
+ #[cfg(feature = "schema")]
749
773
  #[no_mangle]
750
774
  pub unsafe extern "C" fn yerba_document_validate_schema(document: *const Document, schema_json: *const c_char, selector: *const c_char) -> *mut c_char {
751
775
  let document = &*document;
@@ -784,6 +808,7 @@ pub unsafe extern "C" fn yerba_document_validate_schema(document: *const Documen
784
808
  }
785
809
 
786
810
  /// Caller must free with yerba_string_free.
811
+ #[cfg(feature = "cli")]
787
812
  #[no_mangle]
788
813
  pub unsafe extern "C" fn yerba_yerbafile_find(directory: *const c_char) -> *mut c_char {
789
814
  let start = if directory.is_null() {
@@ -803,6 +828,7 @@ pub unsafe extern "C" fn yerba_yerbafile_find(directory: *const c_char) -> *mut
803
828
  }
804
829
  }
805
830
 
831
+ #[cfg(feature = "cli")]
806
832
  #[no_mangle]
807
833
  pub unsafe extern "C" fn yerba_document_apply_yerbafile(document: *mut Document, file_path: *const c_char, yerbafile_path: *const c_char) -> YerbaResult {
808
834
  let document = &mut *document;
@@ -840,6 +866,21 @@ pub unsafe extern "C" fn yerba_document_to_string(document: *const Document) ->
840
866
  CString::new(content).unwrap_or_default().into_raw()
841
867
  }
842
868
 
869
+ #[no_mangle]
870
+ pub unsafe extern "C" fn yerba_quote_scalar(value: *const c_char, length: usize, style: *const c_char) -> *mut c_char {
871
+ let bytes = std::slice::from_raw_parts(value as *const u8, length);
872
+
873
+ let Ok(value) = std::str::from_utf8(bytes) else {
874
+ return std::ptr::null_mut();
875
+ };
876
+
877
+ let style = if style.is_null() { None } else { CStr::from_ptr(style).to_str().ok() };
878
+
879
+ CString::new(crate::syntax::quote_scalar_styled(value, style))
880
+ .map(|string| string.into_raw())
881
+ .unwrap_or(std::ptr::null_mut())
882
+ }
883
+
843
884
  #[no_mangle]
844
885
  pub unsafe extern "C" fn yerba_string_free(s: *mut c_char) {
845
886
  if !s.is_null() {
@@ -924,6 +965,7 @@ fn located_nodes_to_list(nodes: &[crate::LocatedNode]) -> YerbaTypedList {
924
965
  }
925
966
  }
926
967
 
968
+ #[cfg(feature = "glob")]
927
969
  #[no_mangle]
928
970
  pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
929
971
  let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
@@ -940,6 +982,7 @@ pub unsafe extern "C" fn yerba_document_get_all(document: *const Document, path:
940
982
  located_nodes_to_list(&document.get_all_located(selector_string))
941
983
  }
942
984
 
985
+ #[cfg(feature = "glob")]
943
986
  #[no_mangle]
944
987
  pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *const c_char, condition: *const c_char, select: *const c_char) -> YerbaTypedList {
945
988
  let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
data/rust/src/lib.rs CHANGED
@@ -4,21 +4,29 @@ pub mod error;
4
4
  pub mod ffi;
5
5
  pub mod json;
6
6
  mod quote_style;
7
+ #[cfg(feature = "schema")]
7
8
  pub mod schema;
8
9
  pub mod selector;
9
10
  mod syntax;
11
+ mod validation;
10
12
  mod yaml_writer;
13
+ #[cfg(feature = "cli")]
11
14
  pub mod yerbafile;
12
15
 
13
- pub use document::style::StyleEnforcement;
16
+ pub use document::style::{BlankLineOptions, StyleEnforcement};
14
17
  pub use document::{
15
18
  collect_selectors, validate_condition, validate_item_condition, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField,
16
19
  };
17
20
  pub use error::YerbaError;
18
21
  pub use quote_style::{KeyStyle, QuoteStyle};
19
22
  pub use selector::Selector;
20
- pub use syntax::{detect_yaml_type, ScalarValue, YerbaValueType};
23
+ pub use syntax::{
24
+ detect_yaml_type, escape_double_quoted, is_control_character, is_flow_collection, is_inline_scalar_safe, is_plain_safe, is_plain_safe_in_flow,
25
+ is_quoted_scalar, is_single_quotable, needs_quoting, needs_quoting_in_flow, quote_scalar, quote_scalar_styled, ScalarValue, YerbaValueType,
26
+ };
27
+ pub use validation::{find_control_characters, ControlCharacter};
21
28
  pub use yaml_writer::json_to_yaml_text;
29
+ #[cfg(feature = "cli")]
22
30
  pub use yerbafile::Yerbafile;
23
31
 
24
32
  pub fn version() -> &'static str {
@@ -33,6 +41,7 @@ pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Document, YerbaEr
33
41
  Document::parse_file(path)
34
42
  }
35
43
 
44
+ #[cfg(feature = "glob")]
36
45
  pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
37
46
  use rayon::prelude::*;
38
47
 
@@ -55,6 +64,7 @@ pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
55
64
  .collect()
56
65
  }
57
66
 
67
+ #[cfg(feature = "glob")]
58
68
  pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select: Option<&str>) -> Result<Vec<serde_json::Value>, YerbaError> {
59
69
  use rayon::prelude::*;
60
70