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.
- checksums.yaml +4 -4
- data/README.md +170 -19
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +7 -0
- data/ext/yerba/yerba.c +54 -5
- 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 +9 -13
- data/lib/yerba/map.rb +11 -8
- data/lib/yerba/sequence.rb +2 -7
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +2 -342
- data/rust/Cargo.toml +15 -6
- data/rust/src/commands/blank_lines.rs +23 -3
- data/rust/src/commands/directives.rs +4 -4
- data/rust/src/commands/get.rs +7 -10
- data/rust/src/commands/init.rs +10 -4
- data/rust/src/commands/insert.rs +18 -12
- data/rust/src/commands/location.rs +2 -2
- data/rust/src/commands/mate.rs +7 -7
- data/rust/src/commands/mod.rs +118 -90
- data/rust/src/commands/move_key.rs +9 -6
- data/rust/src/commands/quote_style.rs +4 -5
- data/rust/src/commands/schema.rs +6 -6
- data/rust/src/commands/selectors.rs +2 -2
- data/rust/src/commands/set.rs +18 -7
- data/rust/src/commands/sort.rs +36 -42
- data/rust/src/commands/sort_keys.rs +2 -2
- data/rust/src/commands/ui.rs +156 -0
- data/rust/src/commands/unique.rs +7 -7
- data/rust/src/commands/version.rs +13 -3
- data/rust/src/document/insert.rs +25 -9
- data/rust/src/document/mod.rs +7 -3
- data/rust/src/document/set.rs +115 -33
- data/rust/src/document/style.rs +131 -11
- data/rust/src/error.rs +27 -0
- data/rust/src/ffi.rs +55 -12
- data/rust/src/lib.rs +12 -2
- data/rust/src/quote_style.rs +12 -11
- data/rust/src/syntax.rs +285 -11
- data/rust/src/validation.rs +30 -0
- data/rust/src/yaml_writer.rs +9 -21
- data/rust/src/yerbafile.rs +76 -4
- metadata +4 -2
data/rust/src/document/set.rs
CHANGED
|
@@ -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
|
-
|
|
116
|
+
self.set_with(dot_path, value, false)
|
|
117
|
+
}
|
|
71
118
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
130
|
+
if nodes.is_empty() {
|
|
131
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
83
132
|
}
|
|
84
133
|
|
|
85
|
-
let
|
|
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.
|
|
137
|
+
self.apply_edits(edits)
|
|
90
138
|
}
|
|
91
139
|
|
|
92
|
-
pub fn
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
for
|
|
103
|
-
if
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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(¤t_node) {
|
|
147
222
|
let span = value_span(¤t_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,
|
|
225
|
+
return self.apply_edit(span.range, &replacement_text(&span, &replacement));
|
|
150
226
|
}
|
|
151
227
|
|
|
152
228
|
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
153
229
|
|
|
154
|
-
|
|
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 {
|
data/rust/src/document/style.rs
CHANGED
|
@@ -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
|
|
601
|
-
Some(FirstCollection::Sequence(sequence)) => sequence
|
|
602
|
-
|
|
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
|
|
607
|
-
|
|
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 =
|
|
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 =
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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 =
|
|
539
|
+
let raw_value = borrow_value(value, value_length);
|
|
528
540
|
|
|
529
541
|
let value_string = match value_type {
|
|
530
|
-
YerbaValueType::String =>
|
|
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
|
-
|
|
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::{
|
|
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
|
|