yerba 0.2.2 → 0.4.0
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 +167 -12
- data/ext/yerba/extconf.rb +39 -12
- data/ext/yerba/include/yerba.h +21 -10
- data/ext/yerba/yerba.c +91 -25
- data/lib/yerba/collection.rb +35 -0
- data/lib/yerba/document.rb +16 -0
- data/lib/yerba/sequence.rb +169 -1
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +7 -2
- data/rust/Cargo.lock +1 -0
- data/rust/Cargo.toml +2 -2
- data/rust/cbindgen.toml +1 -0
- data/rust/rustfmt.toml +1 -1
- data/rust/src/commands/blank_lines.rs +1 -4
- data/rust/src/commands/delete.rs +9 -4
- data/rust/src/commands/directives.rs +61 -0
- data/rust/src/commands/get.rs +52 -26
- data/rust/src/commands/insert.rs +8 -4
- data/rust/src/commands/mod.rs +71 -9
- data/rust/src/commands/move_item.rs +2 -1
- data/rust/src/commands/move_key.rs +2 -1
- data/rust/src/commands/quote_style.rs +12 -6
- data/rust/src/commands/remove.rs +8 -4
- data/rust/src/commands/rename.rs +8 -4
- data/rust/src/commands/selectors.rs +158 -0
- data/rust/src/commands/set.rs +33 -16
- data/rust/src/commands/sort.rs +342 -10
- data/rust/src/didyoumean.rs +53 -0
- data/rust/src/document/condition.rs +139 -0
- data/rust/src/document/delete.rs +91 -0
- data/rust/src/document/get.rs +262 -0
- data/rust/src/document/insert.rs +314 -0
- data/rust/src/document/mod.rs +784 -0
- data/rust/src/document/set.rs +90 -0
- data/rust/src/document/sort.rs +607 -0
- data/rust/src/document/style.rs +473 -0
- data/rust/src/error.rs +35 -7
- data/rust/src/ffi.rs +213 -520
- data/rust/src/json.rs +1 -7
- data/rust/src/lib.rs +89 -2
- data/rust/src/main.rs +2 -0
- data/rust/src/quote_style.rs +83 -7
- data/rust/src/selector.rs +2 -7
- data/rust/src/syntax.rs +41 -21
- data/rust/src/yerbafile.rs +39 -18
- metadata +12 -2
- data/rust/Cargo.lock +0 -805
- data/rust/src/document.rs +0 -2237
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn get(&self, dot_path: &str) -> Option<String> {
|
|
5
|
+
if dot_path.contains('[') {
|
|
6
|
+
return self.get_all(dot_path).into_iter().next();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let current_node = self.navigate(dot_path).ok()?;
|
|
10
|
+
|
|
11
|
+
extract_scalar_text(¤t_node)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn get_all(&self, dot_path: &str) -> Vec<String> {
|
|
15
|
+
self.navigate_all(dot_path).iter().filter_map(extract_scalar_text).collect()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn get_typed(&self, dot_path: &str) -> Option<ScalarValue> {
|
|
19
|
+
if crate::selector::Selector::parse(dot_path).has_wildcard() {
|
|
20
|
+
return self.get_all_typed(dot_path).into_iter().next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let current_node = self.navigate(dot_path).ok()?;
|
|
24
|
+
|
|
25
|
+
if current_node
|
|
26
|
+
.descendants()
|
|
27
|
+
.any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
|
|
28
|
+
{
|
|
29
|
+
return None;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
extract_scalar(¤t_node)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn get_all_typed(&self, dot_path: &str) -> Vec<ScalarValue> {
|
|
36
|
+
self
|
|
37
|
+
.navigate_all(dot_path)
|
|
38
|
+
.iter()
|
|
39
|
+
.filter(|node| {
|
|
40
|
+
!node
|
|
41
|
+
.descendants()
|
|
42
|
+
.any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
|
|
43
|
+
})
|
|
44
|
+
.filter_map(extract_scalar)
|
|
45
|
+
.collect()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn node_type(&self, dot_path: &str) -> NodeType {
|
|
49
|
+
match self.navigate(dot_path) {
|
|
50
|
+
Ok(node) => {
|
|
51
|
+
if let Some(first_structural) = node
|
|
52
|
+
.descendants()
|
|
53
|
+
.find(|child| BlockMap::can_cast(child.kind()) || BlockSeq::can_cast(child.kind()))
|
|
54
|
+
{
|
|
55
|
+
if BlockMap::can_cast(first_structural.kind()) {
|
|
56
|
+
NodeType::Map
|
|
57
|
+
} else {
|
|
58
|
+
NodeType::Sequence
|
|
59
|
+
}
|
|
60
|
+
} else if extract_scalar(&node).is_some() {
|
|
61
|
+
NodeType::Scalar
|
|
62
|
+
} else {
|
|
63
|
+
NodeType::NotFound
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
Err(_) => NodeType::NotFound,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn get_node_info(&self, dot_path: &str) -> NodeInfo {
|
|
71
|
+
let selector = crate::selector::Selector::parse(dot_path);
|
|
72
|
+
let source = self.root.text().to_string();
|
|
73
|
+
|
|
74
|
+
let (location, key_name, key_location) = self.resolve_location(dot_path, &source);
|
|
75
|
+
|
|
76
|
+
if selector.has_wildcard() {
|
|
77
|
+
let values = self.get_all_typed(dot_path);
|
|
78
|
+
|
|
79
|
+
return NodeInfo {
|
|
80
|
+
node_type: NodeType::Sequence,
|
|
81
|
+
is_list: true,
|
|
82
|
+
value: None,
|
|
83
|
+
list_values: values,
|
|
84
|
+
location,
|
|
85
|
+
key_name,
|
|
86
|
+
key_location,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
match self.get_typed(dot_path) {
|
|
91
|
+
Some(scalar) => NodeInfo {
|
|
92
|
+
node_type: NodeType::Scalar,
|
|
93
|
+
is_list: false,
|
|
94
|
+
value: Some(scalar),
|
|
95
|
+
list_values: vec![],
|
|
96
|
+
location,
|
|
97
|
+
key_name,
|
|
98
|
+
key_location,
|
|
99
|
+
},
|
|
100
|
+
None => NodeInfo {
|
|
101
|
+
node_type: self.node_type(dot_path),
|
|
102
|
+
is_list: false,
|
|
103
|
+
value: None,
|
|
104
|
+
list_values: vec![],
|
|
105
|
+
location,
|
|
106
|
+
key_name,
|
|
107
|
+
key_location,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn resolve_location(&self, dot_path: &str, source: &str) -> (Location, Option<String>, Location) {
|
|
113
|
+
match self.navigate(dot_path) {
|
|
114
|
+
Ok(node) => {
|
|
115
|
+
let range = node.text_range();
|
|
116
|
+
let location = compute_location(source, range.start().into(), range.end().into());
|
|
117
|
+
|
|
118
|
+
let (key_name, key_location) = node
|
|
119
|
+
.parent()
|
|
120
|
+
.and_then(|parent| {
|
|
121
|
+
use yaml_parser::ast::BlockMapEntry;
|
|
122
|
+
|
|
123
|
+
BlockMapEntry::cast(parent).and_then(|entry| {
|
|
124
|
+
entry.key().and_then(|key_node| {
|
|
125
|
+
let key_text = extract_scalar_text(key_node.syntax())?;
|
|
126
|
+
let key_range = key_node.syntax().text_range();
|
|
127
|
+
let key_location = compute_location(source, key_range.start().into(), key_range.end().into());
|
|
128
|
+
|
|
129
|
+
Some((key_text, key_location))
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
.map(|(name, location)| (Some(name), location))
|
|
134
|
+
.unwrap_or((None, Location::default()));
|
|
135
|
+
|
|
136
|
+
(location, key_name, key_location)
|
|
137
|
+
}
|
|
138
|
+
Err(_) => (Location::default(), None, Location::default()),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub fn find_items(&self, dot_path: &str, condition: Option<&str>, select: Option<&str>) -> Vec<serde_json::Value> {
|
|
143
|
+
let values = match condition {
|
|
144
|
+
Some(cond) => self.filter(dot_path, cond),
|
|
145
|
+
None => self.get_values(dot_path),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let select_fields: Option<Vec<&str>> = select.map(|s| s.split(',').collect());
|
|
149
|
+
|
|
150
|
+
values
|
|
151
|
+
.iter()
|
|
152
|
+
.map(|value| match &select_fields {
|
|
153
|
+
Some(fields) => {
|
|
154
|
+
let mut result = serde_json::Map::new();
|
|
155
|
+
|
|
156
|
+
for field in fields {
|
|
157
|
+
let json_value = crate::json::resolve_select_field(value, field);
|
|
158
|
+
let json_key = crate::json::select_field_key(field);
|
|
159
|
+
result.insert(json_key, json_value);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
serde_json::Value::Object(result)
|
|
163
|
+
}
|
|
164
|
+
None => crate::json::yaml_to_json(value),
|
|
165
|
+
})
|
|
166
|
+
.collect()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub fn get_value(&self, dot_path: &str) -> Option<serde_yaml::Value> {
|
|
170
|
+
if dot_path.is_empty() {
|
|
171
|
+
return Some(node_to_yaml_value(&self.root));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let nodes = self.navigate_all(dot_path);
|
|
175
|
+
|
|
176
|
+
if nodes.is_empty() {
|
|
177
|
+
return None;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if nodes.len() == 1 {
|
|
181
|
+
return Some(node_to_yaml_value(&nodes[0]));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let values: Vec<serde_yaml::Value> = nodes.iter().map(node_to_yaml_value).collect();
|
|
185
|
+
|
|
186
|
+
Some(serde_yaml::Value::Sequence(values))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
pub fn get_values(&self, dot_path: &str) -> Vec<serde_yaml::Value> {
|
|
190
|
+
self.navigate_all(dot_path).iter().map(node_to_yaml_value).collect()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pub fn selectors(&self) -> Vec<String> {
|
|
194
|
+
let Some(value) = self.get_value("") else {
|
|
195
|
+
return Vec::new();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
let mut selectors = Vec::new();
|
|
199
|
+
|
|
200
|
+
collect_selectors(&value, "", &mut selectors);
|
|
201
|
+
selectors.sort();
|
|
202
|
+
selectors.dedup();
|
|
203
|
+
|
|
204
|
+
selectors
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pub fn exists(&self, dot_path: &str) -> bool {
|
|
208
|
+
if dot_path.contains('[') {
|
|
209
|
+
return !self.navigate_all(dot_path).is_empty();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
self.get(dot_path).is_some()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
pub fn get_sequence_values(&self, dot_path: &str) -> Vec<String> {
|
|
216
|
+
let current_node = match self.navigate(dot_path) {
|
|
217
|
+
Ok(node) => node,
|
|
218
|
+
Err(_) => return Vec::new(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
|
|
222
|
+
Some(sequence) => sequence,
|
|
223
|
+
None => return Vec::new(),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
sequence
|
|
227
|
+
.entries()
|
|
228
|
+
.filter_map(|entry| entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())))
|
|
229
|
+
.collect()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
pub fn get_quote_style(&self, dot_path: &str) -> Option<&'static str> {
|
|
233
|
+
let current_node = self.navigate(dot_path).ok()?;
|
|
234
|
+
|
|
235
|
+
for element in current_node.descendants_with_tokens() {
|
|
236
|
+
match element.kind() {
|
|
237
|
+
SyntaxKind::PLAIN_SCALAR => return Some("plain"),
|
|
238
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => return Some("single"),
|
|
239
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => return Some("double"),
|
|
240
|
+
|
|
241
|
+
SyntaxKind::BLOCK_SCALAR => {
|
|
242
|
+
let text = element.as_node()?.text().to_string();
|
|
243
|
+
let header = text.lines().next().unwrap_or("").trim();
|
|
244
|
+
|
|
245
|
+
return match header {
|
|
246
|
+
"|-" => Some("literal"),
|
|
247
|
+
"|" => Some("literal-clip"),
|
|
248
|
+
"|+" => Some("literal-keep"),
|
|
249
|
+
">-" => Some("folded"),
|
|
250
|
+
">" => Some("folded-clip"),
|
|
251
|
+
">+" => Some("folded-keep"),
|
|
252
|
+
_ => None,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_ => {}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
None
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Document {
|
|
4
|
+
pub fn append(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
5
|
+
self.insert_into(dot_path, value, InsertPosition::Last)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
pub fn detect_sequence_quote_style(&self, dot_path: &str) -> QuoteStyle {
|
|
9
|
+
let try_paths = if dot_path.is_empty() {
|
|
10
|
+
vec!["[]".to_string(), "[0]".to_string()]
|
|
11
|
+
} else {
|
|
12
|
+
vec![format!("{}[]", dot_path), format!("{}[0]", dot_path)]
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for try_path in &try_paths {
|
|
16
|
+
for scalar in self.get_all_typed(try_path) {
|
|
17
|
+
if scalar.kind == SyntaxKind::DOUBLE_QUOTED_SCALAR {
|
|
18
|
+
return QuoteStyle::Double;
|
|
19
|
+
} else if scalar.kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
|
|
20
|
+
return QuoteStyle::Single;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if let Some(serde_yaml::Value::Sequence(sequence)) = self.get_value(dot_path).as_ref() {
|
|
26
|
+
if let Some(serde_yaml::Value::Mapping(map)) = sequence.first() {
|
|
27
|
+
if let Some((serde_yaml::Value::String(key_name), _)) = map.iter().next() {
|
|
28
|
+
let deep_path = if dot_path.is_empty() {
|
|
29
|
+
format!("[].{}", key_name)
|
|
30
|
+
} else {
|
|
31
|
+
format!("{}[].{}", dot_path, key_name)
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for scalar in self.get_all_typed(&deep_path) {
|
|
35
|
+
if scalar.kind == SyntaxKind::DOUBLE_QUOTED_SCALAR {
|
|
36
|
+
return QuoteStyle::Double;
|
|
37
|
+
} else if scalar.kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
|
|
38
|
+
return QuoteStyle::Single;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
QuoteStyle::Plain
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn insert_object(&mut self, dot_path: &str, json_value: &serde_json::Value, position: InsertPosition) -> Result<(), YerbaError> {
|
|
49
|
+
let quote_style = self.detect_sequence_quote_style(dot_path);
|
|
50
|
+
let yaml_text = crate::yaml_writer::json_to_yaml_text(json_value, "e_style, 0);
|
|
51
|
+
|
|
52
|
+
self.insert_into(dot_path, &yaml_text, position)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
56
|
+
Self::validate_path(dot_path)?;
|
|
57
|
+
|
|
58
|
+
if let Ok(current_node) = self.navigate(dot_path) {
|
|
59
|
+
if current_node.descendants().find_map(BlockSeq::cast).is_some() {
|
|
60
|
+
return self.insert_sequence_item(dot_path, value, position);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let (parent_path, key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
|
|
65
|
+
|
|
66
|
+
self.insert_map_key(parent_path, key, value, position)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn insert_sequence_item(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
70
|
+
let current_node = self.navigate(dot_path)?;
|
|
71
|
+
|
|
72
|
+
let sequence = current_node
|
|
73
|
+
.descendants()
|
|
74
|
+
.find_map(BlockSeq::cast)
|
|
75
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
76
|
+
|
|
77
|
+
let entries: Vec<_> = sequence.entries().collect();
|
|
78
|
+
|
|
79
|
+
if entries.is_empty() {
|
|
80
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let indent = entries
|
|
84
|
+
.get(1)
|
|
85
|
+
.or(entries.first())
|
|
86
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
87
|
+
.unwrap_or_default();
|
|
88
|
+
|
|
89
|
+
let new_item = if value.contains('\n') {
|
|
90
|
+
let item_indent = format!("{} ", indent);
|
|
91
|
+
let lines: Vec<&str> = value.split('\n').collect();
|
|
92
|
+
|
|
93
|
+
let min_indent = lines
|
|
94
|
+
.iter()
|
|
95
|
+
.skip(1)
|
|
96
|
+
.filter(|line| !line.trim().is_empty())
|
|
97
|
+
.map(|line| line.len() - line.trim_start().len())
|
|
98
|
+
.min()
|
|
99
|
+
.unwrap_or(0);
|
|
100
|
+
|
|
101
|
+
let indented: Vec<String> = lines
|
|
102
|
+
.iter()
|
|
103
|
+
.enumerate()
|
|
104
|
+
.map(|(index, line)| {
|
|
105
|
+
if index == 0 {
|
|
106
|
+
line.to_string()
|
|
107
|
+
} else if line.trim().is_empty() {
|
|
108
|
+
String::new()
|
|
109
|
+
} else {
|
|
110
|
+
let relative = &line[min_indent..];
|
|
111
|
+
format!("{}{}", item_indent, relative)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.collect();
|
|
115
|
+
|
|
116
|
+
format!("- {}", indented.join("\n"))
|
|
117
|
+
} else {
|
|
118
|
+
format!("- {}", value)
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
match position {
|
|
122
|
+
InsertPosition::Last => {
|
|
123
|
+
let last_entry = entries.last().unwrap();
|
|
124
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
125
|
+
|
|
126
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
InsertPosition::At(index) => {
|
|
130
|
+
if index >= entries.len() {
|
|
131
|
+
let last_entry = entries.last().unwrap();
|
|
132
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
133
|
+
|
|
134
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
135
|
+
} else {
|
|
136
|
+
let target_entry = &entries[index];
|
|
137
|
+
let target_range = target_entry.syntax().text_range();
|
|
138
|
+
let replacement = format!("{}\n{}", new_item, indent);
|
|
139
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
140
|
+
|
|
141
|
+
self.apply_edit(insert_range, &replacement)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
InsertPosition::Before(target_value) => {
|
|
146
|
+
let target_entry = entries
|
|
147
|
+
.iter()
|
|
148
|
+
.find(|entry| {
|
|
149
|
+
entry
|
|
150
|
+
.flow()
|
|
151
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
152
|
+
.map(|text| text == target_value)
|
|
153
|
+
.unwrap_or(false)
|
|
154
|
+
})
|
|
155
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} item '{}'", dot_path, target_value)))?;
|
|
156
|
+
|
|
157
|
+
let target_range = target_entry.syntax().text_range();
|
|
158
|
+
let replacement = format!("{}\n{}", new_item, indent);
|
|
159
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
160
|
+
|
|
161
|
+
self.apply_edit(insert_range, &replacement)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
InsertPosition::After(target_value) => {
|
|
165
|
+
let target_entry = entries
|
|
166
|
+
.iter()
|
|
167
|
+
.find(|entry| {
|
|
168
|
+
entry
|
|
169
|
+
.flow()
|
|
170
|
+
.and_then(|flow| extract_scalar_text(flow.syntax()))
|
|
171
|
+
.map(|text| text == target_value)
|
|
172
|
+
.unwrap_or(false)
|
|
173
|
+
})
|
|
174
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} item '{}'", dot_path, target_value)))?;
|
|
175
|
+
|
|
176
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
177
|
+
|
|
178
|
+
self.insert_after_node(target_entry.syntax(), &new_text)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
InsertPosition::BeforeCondition(condition) => {
|
|
182
|
+
let target_entry = entries
|
|
183
|
+
.iter()
|
|
184
|
+
.find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
|
|
185
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} condition '{}'", dot_path, condition)))?;
|
|
186
|
+
|
|
187
|
+
let target_range = target_entry.syntax().text_range();
|
|
188
|
+
let replacement = format!("{}\n{}", new_item, indent);
|
|
189
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
190
|
+
|
|
191
|
+
self.apply_edit(insert_range, &replacement)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
InsertPosition::AfterCondition(condition) => {
|
|
195
|
+
let target_entry = entries
|
|
196
|
+
.iter()
|
|
197
|
+
.find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
|
|
198
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(format!("{} condition '{}'", dot_path, condition)))?;
|
|
199
|
+
|
|
200
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
201
|
+
|
|
202
|
+
self.insert_after_node(target_entry.syntax(), &new_text)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
InsertPosition::FromSortOrder(_) => {
|
|
206
|
+
let last_entry = entries.last().unwrap();
|
|
207
|
+
let new_text = format!("\n{}{}", indent, new_item);
|
|
208
|
+
|
|
209
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn insert_map_key(&mut self, dot_path: &str, key: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
215
|
+
let current_node = self.navigate(dot_path)?;
|
|
216
|
+
|
|
217
|
+
let map = current_node
|
|
218
|
+
.descendants()
|
|
219
|
+
.find_map(BlockMap::cast)
|
|
220
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
221
|
+
|
|
222
|
+
let entries: Vec<_> = map.entries().collect();
|
|
223
|
+
|
|
224
|
+
if entries.is_empty() {
|
|
225
|
+
let indent = preceding_whitespace_indent(map.syntax());
|
|
226
|
+
let new_entry = format!("\n{}{}: {}", indent, key, value);
|
|
227
|
+
|
|
228
|
+
return self.insert_after_node(map.syntax(), &new_entry);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if find_entry_by_key(&map, key).is_some() {
|
|
232
|
+
return Err(YerbaError::ParseError(format!("key '{}' already exists at '{}'", key, dot_path)));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let indent = entries
|
|
236
|
+
.get(1)
|
|
237
|
+
.or(entries.first())
|
|
238
|
+
.map(|entry| preceding_whitespace_indent(entry.syntax()))
|
|
239
|
+
.unwrap_or_default();
|
|
240
|
+
|
|
241
|
+
let new_entry_text = format!("{}: {}", key, value);
|
|
242
|
+
|
|
243
|
+
match position {
|
|
244
|
+
InsertPosition::Last => {
|
|
245
|
+
let last_entry = entries.last().unwrap();
|
|
246
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
247
|
+
|
|
248
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
InsertPosition::At(index) => {
|
|
252
|
+
if index >= entries.len() {
|
|
253
|
+
let last_entry = entries.last().unwrap();
|
|
254
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
255
|
+
|
|
256
|
+
self.insert_after_node(last_entry.syntax(), &new_text)
|
|
257
|
+
} else {
|
|
258
|
+
let target_entry = &entries[index];
|
|
259
|
+
let target_range = target_entry.syntax().text_range();
|
|
260
|
+
let replacement = format!("{}\n{}", new_entry_text, indent);
|
|
261
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
262
|
+
|
|
263
|
+
self.apply_edit(insert_range, &replacement)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
InsertPosition::Before(target_key) => {
|
|
268
|
+
let target_entry = find_entry_by_key(&map, &target_key).ok_or_else(|| YerbaError::SelectorNotFound(format!("{}.{}", dot_path, target_key)))?;
|
|
269
|
+
|
|
270
|
+
let target_range = target_entry.syntax().text_range();
|
|
271
|
+
let replacement = format!("{}\n{}", new_entry_text, indent);
|
|
272
|
+
let insert_range = TextRange::new(target_range.start(), target_range.start());
|
|
273
|
+
|
|
274
|
+
self.apply_edit(insert_range, &replacement)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
InsertPosition::After(target_key) => {
|
|
278
|
+
let target_entry = find_entry_by_key(&map, &target_key).ok_or_else(|| YerbaError::SelectorNotFound(format!("{}.{}", dot_path, target_key)))?;
|
|
279
|
+
|
|
280
|
+
let new_text = format!("\n{}{}", indent, new_entry_text);
|
|
281
|
+
|
|
282
|
+
self.insert_after_node(target_entry.syntax(), &new_text)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
InsertPosition::BeforeCondition(_) | InsertPosition::AfterCondition(_) => self.insert_map_key(dot_path, key, value, InsertPosition::Last),
|
|
286
|
+
|
|
287
|
+
InsertPosition::FromSortOrder(order) => {
|
|
288
|
+
let new_key_position = order.iter().position(|ordered_key| ordered_key == key);
|
|
289
|
+
|
|
290
|
+
let resolved = match new_key_position {
|
|
291
|
+
Some(new_position) => {
|
|
292
|
+
let mut insert_after: Option<String> = None;
|
|
293
|
+
|
|
294
|
+
for ordered_key in order.iter().take(new_position).rev() {
|
|
295
|
+
if find_entry_by_key(&map, ordered_key).is_some() {
|
|
296
|
+
insert_after = Some(ordered_key.clone());
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
match insert_after {
|
|
302
|
+
Some(after_key) => InsertPosition::After(after_key),
|
|
303
|
+
None => InsertPosition::At(0),
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
None => InsertPosition::Last,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
self.insert_map_key(dot_path, key, value, resolved)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|